diff --git a/Firely.Validator.API.sln b/Firely.Validator.API.sln index 08e68bf2..92283642 100644 --- a/Firely.Validator.API.sln +++ b/Firely.Validator.API.sln @@ -8,8 +8,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{32AC4A3D-A456-4CAC-8458-B0F20103F669}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - firely-validator-api.props = firely-validator-api.props firely-validator-api-tests.props = firely-validator-api-tests.props + firely-validator-api.props = firely-validator-api.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7D9F5C18-2827-4975-8FBF-DD45758075AE}" @@ -27,11 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{1B02120E-C942-448D-A444-6F7EB1816C95}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Hl7.Fhir.Validation.Shared", "src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R5.Tests", "test\Hl7.Fhir.Validation.Tests\Hl7.Fhir.Validation.R5.Tests.csproj", "{533FADED-DC64-408D-AF93-E7AE8D9DAF56}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R4", "src\Hl7.Fhir.Validation.R4\Hl7.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.R4", "src\Firely.Fhir.Validation.R4\Firely.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Compilation.Shared", "src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.shproj", "{7794C7D2-EA50-405D-A61A-23F8ECF4228B}" EndProject @@ -45,6 +41,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Compilation.R4.Tests", "test\Firely.Fhir.Validation.Compilation.Tests.R4\Firely.Fhir.Validation.Compilation.R4.Tests.csproj", "{B5968107-6E13-46C1-AB04-7FCEFFF179C7}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Shared", "src\Firely.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.STU3", "src\Firely.Fhir.Validation.STU3\Firely.Fhir.Validation.STU3.csproj", "{4C9C5275-0C05-4CDE-A74C-1B43245252FB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,12 +76,6 @@ Global {1B02120E-C942-448D-A444-6F7EB1816C95}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {1B02120E-C942-448D-A444-6F7EB1816C95}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B02120E-C942-448D-A444-6F7EB1816C95}.Release|Any CPU.Build.0 = Release|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Release|Any CPU.Build.0 = Release|Any CPU {22520A59-7EBF-4740-A66C-3651742FAC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22520A59-7EBF-4740-A66C-3651742FAC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU {22520A59-7EBF-4740-A66C-3651742FAC0B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU @@ -112,6 +106,12 @@ Global {B5968107-6E13-46C1-AB04-7FCEFFF179C7}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {B5968107-6E13-46C1-AB04-7FCEFFF179C7}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5968107-6E13-46C1-AB04-7FCEFFF179C7}.Release|Any CPU.Build.0 = Release|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -119,7 +119,6 @@ Global GlobalSection(NestedProjects) = preSolution {AC070065-936C-4266-9652-5B967C1C4E1F} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {1B02120E-C942-448D-A444-6F7EB1816C95} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} - {533FADED-DC64-408D-AF93-E7AE8D9DAF56} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {DFEFC934-BF53-4CD2-9C05-992062378F2A} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {994A42C0-396B-48B6-81A0-4E622460ECAB} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {F0695E43-0242-4D14-BFF7-4D0F36B0DAD8} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} @@ -130,8 +129,8 @@ Global EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{052b9175-3a5b-46ca-b677-8d082c9a6063}*SharedItemsImports = 5 - src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{21bf806c-bbe6-4a33-91f9-bf3ab5f1e70b}*SharedItemsImports = 13 - src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{22520a59-7ebf-4740-a66c-3651742fac0b}*SharedItemsImports = 5 + src\Firely.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.projitems*{22520a59-7ebf-4740-a66c-3651742fac0b}*SharedItemsImports = 5 + src\Firely.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.projitems*{4c9c5275-0c05-4cde-a74c-1b43245252fb}*SharedItemsImports = 5 src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{7391862b-ac99-40c5-a616-dc83edd83a88}*SharedItemsImports = 5 src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{7794c7d2-ea50-405d-a61a-23f8ecf4228b}*SharedItemsImports = 13 test\Firely.Fhir.Validation.Compilation.Tests.Shared\Firely.Fhir.Validation.Compilation.Tests.Shared.projitems*{994a42c0-396b-48b6-81a0-4e622460ecab}*SharedItemsImports = 5 diff --git a/build/variables.yml b/build/variables.yml index 85d0781f..a801d89c 100644 --- a/build/variables.yml +++ b/build/variables.yml @@ -5,6 +5,6 @@ variables: buildConfiguration: 'Release' vmImage: 'windows-latest' - DOTNET_CORE_SDK: '6.0.x' + DOTNET_CORE_SDK: '8.0.x' GITHUB_PACKAGES_APIKEY: $(GitHubPushPackagesAPIKey) # key is set in variable group APIKeys NUGET_APIKEY: $(NuGetAPIKey) # key is set in variable group APIKeys \ No newline at end of file diff --git a/firely-validator-api-tests.props b/firely-validator-api-tests.props index 7e6edb01..3c3c6f72 100644 --- a/firely-validator-api-tests.props +++ b/firely-validator-api-tests.props @@ -2,7 +2,7 @@ true - net6.0 + net8.0 false 1591 @@ -10,7 +10,7 @@ - 5.3.0 + 5.4.1-20231207.3 5.1.0 diff --git a/firely-validator-api.props b/firely-validator-api.props index e7a5a2d4..2ea802d8 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -2,11 +2,11 @@ - 1.1.1 + 1.2.0-alpha5 Firely Firely (https://fire.ly) Copyright 2015-2022 Firely - netstandard2.1 + net8.0 https://github.com/FirelyTeam/firely-serverless-validator @@ -19,7 +19,7 @@ - 5.3.0 + 5.4.1-20231207.3 @@ -28,6 +28,7 @@ true false $(DefineConstants);DEBUG;TRACE + @@ -38,11 +39,14 @@ True snupkg true + 0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1 + NU5104 CS4014 + NU5104 diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj b/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj index d453302b..745898f1 100644 --- a/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj +++ b/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj @@ -15,9 +15,12 @@ + - + + + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs b/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs index a5300c31..6159dfcf 100644 --- a/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs +++ b/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs @@ -13,10 +13,10 @@ #if DEBUG [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests")] -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests")] #endif #if RELEASE [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] #endif diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..382aad42 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +#nullable enable +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.Nested.get -> Hl7.Fhir.Specification.Source.IAsyncResourceResolver! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.StructureDefinitionCorrectionsResolver(Hl7.Fhir.Specification.Source.ISyncOrAsyncResourceResolver! nested) -> void diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs b/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs index a451ab9d..dccee74d 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs @@ -5,7 +5,6 @@ */ using Hl7.Fhir.Model; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs b/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs index d3b122a1..2c1e0e5c 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs @@ -9,7 +9,6 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs b/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs index e65dbc01..dbd29c9b 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation.Compilation /// Determines which kind of schema we want to generate from the /// element. /// - public enum ElementConversionMode + internal enum ElementConversionMode { /// /// Generate a schema which includes all constraints represented diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs index 9155977e..4bfd11a8 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs @@ -6,7 +6,7 @@ namespace Firely.Fhir.Validation.Compilation /// /// The schema builder for the . /// - public class ElementDefinitionBuilder : ISchemaBuilder + internal class ElementDefinitionBuilder : ISchemaBuilder { /// public IEnumerable Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode = ElementConversionMode.Full) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs index 69db32c2..83a64ced 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs @@ -6,7 +6,7 @@ namespace Firely.Fhir.Validation.Compilation /// /// The schema builder for the . /// - public class StructureDefinitionBuilder : ISchemaBuilder + internal class StructureDefinitionBuilder : ISchemaBuilder { /// public IEnumerable Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode = ElementConversionMode.Full) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs index d65114ac..e3223fb3 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation.Compilation /// /// An interface for implementing a schema builder. Utilize this to extend the schema. /// - public interface ISchemaBuilder + internal interface ISchemaBuilder { /// /// Constucts a schema block. diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs index bd3dbc6d..fb1a92dd 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs @@ -9,7 +9,6 @@ using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; @@ -21,7 +20,7 @@ namespace Firely.Fhir.Validation.Compilation /// Converts the constraints in a to an /// , which can then be used for validation. /// - public class SchemaBuilder : ISchemaBuilder + internal class SchemaBuilder : ISchemaBuilder { /// /// The resolver to use when the under conversion diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs index 7822426e..09870377 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation.Compilation /// you can directly implement the /// method using the parameter instead of the . /// - public abstract class BaseSchemaBuilder : ISchemaBuilder + internal abstract class BaseSchemaBuilder : ISchemaBuilder { /// public IEnumerable Build(ElementDefinitionNavigator nav, diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs index cc71f7dc..b67dd569 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs @@ -6,7 +6,6 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; -using Hl7.Fhir.Validation; using System.Collections.Generic; namespace Firely.Fhir.Validation.Compilation diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs index 23fcfb17..6d71730a 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs @@ -4,8 +4,6 @@ * via any medium is strictly prohibited. */ -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Introspection; using Hl7.Fhir.Specification.Navigation; using System.Collections.Generic; @@ -25,7 +23,7 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv var def = nav.Current; if (def.Fixed is not null) - yield return new FixedValidator(def.Fixed.ToTypedElement(ModelInspector.Base)); + yield return new FixedValidator(def.Fixed); } } } diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs index 24df8106..0f2f863b 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs @@ -4,8 +4,6 @@ * via any medium is strictly prohibited. */ -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Introspection; using Hl7.Fhir.Specification.Navigation; using System.Collections.Generic; @@ -25,7 +23,7 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv var def = nav.Current; if (def.Pattern is not null) - yield return new PatternValidator(def.Pattern.ToTypedElement(ModelInspector.Base)); + yield return new PatternValidator(def.Pattern); } } } diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index 8a78723d..9e44ec9d 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -10,7 +10,6 @@ using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; @@ -68,11 +67,15 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv #else var hasProfileDetails = def.Type.Any(tr => tr.Profile.Any() || tr.TargetProfile.Any()); #endif - if ((!nav.HasChildren || hasProfileDetails) && def.Type.Any()) - yield return ConvertTypeReferences(def.Type); + if ((!nav.HasChildren || hasProfileDetails) && def.Type.Count > 0) + { + var typeAssertions = ConvertTypeReferences(def.Type); + if (typeAssertions is not null) + yield return typeAssertions; + } } - public IAssertion ConvertTypeReferences(IEnumerable typeRefs) + public IAssertion? ConvertTypeReferences(IEnumerable typeRefs) { if (!CommonTypeRefComponent.CanConvert(typeRefs)) throw new IncorrectElementDefinitionException("Encountered an element with typerefs that cannot be converted to a common structure."); @@ -101,12 +104,13 @@ public IAssertion ConvertTypeReferences(IEnumerable null, // If there are no explicit profiles, use the schema associated with the declared type code in the typeref. { Count: 1 } single => new SchemaReferenceValidator(single.Single()), @@ -124,9 +128,11 @@ internal IAssertion ConvertTypeReference(CommonTypeRefComponent typeRef) var targetProfileAssertions = ConvertTargetProfilesToSchemaReferences(targetProfiles); var validateReferenceAssertion = buildvalidateInstance(typeRef.AggregationElement, typeRef.Versioning, targetProfileAssertions); - return new AllValidator(profileAssertions, validateReferenceAssertion); + return profileAssertions is not null + ? new AllValidator(profileAssertions, validateReferenceAssertion) + : validateReferenceAssertion; } - else if (code != "Reference" && code != "canonical" && typeRef.TargetProfile.Any()) + else if (!(code is "Reference" or "canonical" or "CodeableReference") && typeRef.TargetProfile.Any()) { throw new IncorrectElementDefinitionException($"Encountered targetProfiles {string.Join(",", typeRef.TargetProfile)} on an element that is not " + $"a reference type (canonical or Reference) but a {code}."); diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs b/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs index d6fa8681..7b42ccb3 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs @@ -27,7 +27,7 @@ namespace Firely.Fhir.Validation.Compilation /// Also, since schema generation is expensive, this resolver will cache the results /// and return the already-converted schema for the same uri the next time " is called. /// - public class StructureDefinitionToElementSchemaResolver : IElementSchemaResolver // internal? + internal class StructureDefinitionToElementSchemaResolver : IElementSchemaResolver // internal? { private readonly SchemaBuilder _schemaBuilder; @@ -78,7 +78,9 @@ internal StructureDefinitionToElementSchemaResolver(IAsyncResourceResolver sourc /// source StructureDefinition for a schema uri. internal StructureDefinitionToElementSchemaResolver(IAsyncResourceResolver source) : this(source, new[] { new StandardBuilders(source) }) - { } + { + // Nothing + } /// /// Builds a schema directly from an without fetching @@ -93,9 +95,20 @@ internal StructureDefinitionToElementSchemaResolver(IAsyncResourceResolver sourc /// The canonical url of the StructureDefinition. /// The schema, or null if the schema uri could not be resolved as a /// StructureDefinition canonical. - public ElementSchema? GetSchema(Canonical schemaUri) => - TaskHelper.Await(() => Source.FindStructureDefinitionAsync((string)schemaUri)) is StructureDefinition sd - ? _schemaBuilder.BuildSchema(sd) - : null; + public ElementSchema? GetSchema(Canonical schemaUri) + { + try + { + return TaskHelper.Await(() => Source.FindStructureDefinitionAsync((string)schemaUri)) is StructureDefinition sd + ? _schemaBuilder.BuildSchema(sd) + : null; + } + catch (Exception e) + { + throw new SchemaResolutionFailedException( + $"Encountered an error while loading schema '{schemaUri}': {e.Message}", + schemaUri, e); + } + } } } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj b/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj index 67a27455..0284df9c 100644 --- a/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj +++ b/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj @@ -16,9 +16,12 @@ + + + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..382aad42 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +#nullable enable +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.Nested.get -> Hl7.Fhir.Specification.Source.IAsyncResourceResolver! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.StructureDefinitionCorrectionsResolver(Hl7.Fhir.Specification.Source.ISyncOrAsyncResourceResolver! nested) -> void diff --git a/src/Firely.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj b/src/Firely.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj new file mode 100644 index 00000000..fc21496e --- /dev/null +++ b/src/Firely.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj @@ -0,0 +1,23 @@ + + + + + + + + Firely validation library for FHIR R4 and up + Next-gen Firely validator for R4 and up, which validates FHIR data against profiles. + HL7;FHIR;Validation;Utility; + Firely.Fhir.Validation.R4 + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.R4/Properties/AssemblyInfo.cs b/src/Firely.Fhir.Validation.R4/Properties/AssemblyInfo.cs similarity index 100% rename from src/Hl7.Fhir.Validation.R4/Properties/AssemblyInfo.cs rename to src/Firely.Fhir.Validation.R4/Properties/AssemblyInfo.cs diff --git a/src/Firely.Fhir.Validation.R4/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation.R4/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation.R4/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.R4/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation.R4/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..019838f6 --- /dev/null +++ b/src/Firely.Fhir.Validation.R4/PublicAPI.Unshipped.txt @@ -0,0 +1,6 @@ +#nullable enable +Firely.Fhir.Validation.Validator +Firely.Fhir.Validation.Validator.SkipConstraintValidation -> bool +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Hl7.Fhir.Model.OperationOutcome! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Hl7.Fhir.Model.OperationOutcome! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Firely.Fhir.Validation.ValidationContext? settings = null) -> void \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj b/src/Firely.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj new file mode 100644 index 00000000..70d0a66a --- /dev/null +++ b/src/Firely.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj @@ -0,0 +1,23 @@ + + + + + + + + Firely validation library for FHIR STU3 + Next-gen Firely validator for STU3, which validates FHIR data against profiles. + HL7;FHIR;Validation;Utility; + Firely.Fhir.Validation.STU3 + + + + + + + + + + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.STU3/Properties/AssemblyInfo.cs b/src/Firely.Fhir.Validation.STU3/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..e3aead2d --- /dev/null +++ b/src/Firely.Fhir.Validation.STU3/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +using System; +using System.Runtime.CompilerServices; + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: CLSCompliant(true)] diff --git a/src/Firely.Fhir.Validation.STU3/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation.STU3/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation.STU3/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.STU3/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation.STU3/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..9d3adad0 --- /dev/null +++ b/src/Firely.Fhir.Validation.STU3/PublicAPI.Unshipped.txt @@ -0,0 +1,6 @@ +#nullable enable +Firely.Fhir.Validation.Validator +Firely.Fhir.Validation.Validator.SkipConstraintValidation -> bool +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Hl7.Fhir.Model.OperationOutcome! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Hl7.Fhir.Model.OperationOutcome! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Firely.Fhir.Validation.ValidationContext? settings = null) -> void diff --git a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems b/src/Firely.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.projitems similarity index 51% rename from src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems rename to src/Firely.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.projitems index c14f17e6..2ca94092 100644 --- a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems +++ b/src/Firely.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.projitems @@ -6,15 +6,9 @@ 21bf806c-bbe6-4a33-91f9-bf3ab5f1e70b - Hl7.Fhir.Validation.Shared + Firely.Fhir.Validation.Shared - - - - - - \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.shproj b/src/Firely.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.shproj similarity index 100% rename from src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.shproj rename to src/Firely.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.shproj diff --git a/src/Firely.Fhir.Validation.Shared/Validator.cs b/src/Firely.Fhir.Validation.Shared/Validator.cs new file mode 100644 index 00000000..8526d4d0 --- /dev/null +++ b/src/Firely.Fhir.Validation.Shared/Validator.cs @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ +using Firely.Fhir.Validation.Compilation; +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Source; +using Hl7.Fhir.Specification.Terminology; +using Hl7.Fhir.Utility; +using System; + +namespace Firely.Fhir.Validation +{ + /// + /// A FHIR profile validator, which is able to validate a FHIR resource against a given FHIR profile. + /// + public class Validator + { + /// + /// Constructs a validator, passing in the services the validator depends on. + /// + /// An that is used to resolve the StructureDefinitions to validate against. + /// An that is used when the validator must validate a code against a + /// terminology service. + /// A that resolves an url to an external instance, represented as a Model POCO. + /// A that contains settings for the validator. + public Validator( + IAsyncResourceResolver resourceResolver, + ICodeValidationTerminologyService terminologyService, + IExternalReferenceResolver? referenceResolver = null, + ValidationContext? settings = null) + { + var elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); + + _settings = settings ?? new ValidationContext(); + + // Set the internal settings that we have hidden in this high-level API. + _settings.ElementSchemaResolver = elementSchemaResolver; + _settings.ValidateCodeService = terminologyService; + _settings.ResolveExternalReference = referenceResolver is not null ? resolve : null; + + ITypedElement? resolve(string reference, string location) + { + var r = TaskHelper.Await(() => referenceResolver.ResolveAsync(reference)); + return toTypedElement(r); + } + } + + private readonly Predicate _fhirPathFilter = ass => ass is FhirPathValidator; + + private void updateContext() + { + if (SkipConstraintValidation && !_settings.ExcludeFilters.Contains(_fhirPathFilter)) + _settings.ExcludeFilters.Add(_fhirPathFilter); + else if (!SkipConstraintValidation && _settings.ExcludeFilters.Contains(_fhirPathFilter)) + _settings.ExcludeFilters.Remove(_fhirPathFilter); + } + + private static ITypedElement? toTypedElement(object? o) => + o switch + { + null => null, + ElementNode en => en, + Resource r => r.ToTypedElement(ModelInfo.ModelInspector), + _ => throw new ArgumentException("Reference resolver must return either a Resource or ElementNode.") + }; + + private readonly ValidationContext _settings; + + /// + /// StructureDefinition may contain FhirPath constraints to enfore invariants in the data that cannot + /// be expresses using StructureDefinition alone. This validation can be turned off for performance or + /// debugging purposes. Default is 'false'. + /// + public bool SkipConstraintValidation = false; + + /// + /// Validates an instance against a profile. + /// + /// A report containing the issues found during validation. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + // Suppressing this issue since this will be a single method call when we introduce IScopedNode. + public OperationOutcome Validate(Resource instance, string? profile = null) => Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), profile); +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + + /// + /// Validates an instance against a profile. + /// + /// A report containing the issues found during validation. +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public OperationOutcome Validate(ElementNode instance, string? profile = null) => Validate(instance.AsScopedNode(), profile); +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + + internal OperationOutcome Validate(IScopedNode sn, string? profile = null) + { + profile ??= Canonical.ForCoreType(sn.InstanceType).ToString(); + + var validator = new SchemaReferenceValidator(profile); + updateContext(); + return validator.Validate(sn, _settings).ToOperationOutcome(); + } + } +} diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs index bb65cefe..9229a6ae 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs @@ -12,20 +12,20 @@ namespace Firely.Fhir.Validation /// An that represents a FHIR ElementDefinition /// [DataContract] - public class ElementDefinitionValidator : IValidatable + internal class ElementDefinitionValidator : IValidatable { /// public JToken ToJson() => new JProperty("elementDefinition", new JObject()); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { var evidence = new List(); //this can be expanded with other validate functionality evidence.AddRange(validateTypeCompatibilityOfValues(input, state)); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } /// @@ -34,7 +34,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati /// /// /// - private static IEnumerable validateTypeCompatibilityOfValues(ITypedElement input, ValidationState state) + private static IEnumerable validateTypeCompatibilityOfValues(IScopedNode input, ValidationState state) { var typeNames = getTypeNames(input); @@ -65,16 +65,16 @@ private static IEnumerable validateTypeCompatibilityOfValues(IType return Enumerable.Empty(); } - private static IEnumerable getTypeNames(ITypedElement input) + private static IEnumerable getTypeNames(IScopedNode input) { var typeComponents = input.Children("type"); return typeComponents.SelectMany(t => t.Children("code") .TakeWhile(c => c.Value?.ToString() is not null)) - .Select(c => c.Value.ToString()); + .Select(c => c.Value.ToString()!); } - private static IEnumerable validateType(IEnumerable valueTypes, string propertyName, IEnumerable typeNames, ITypedElement input, ValidationState state) + private static IEnumerable validateType(IEnumerable valueTypes, string propertyName, IEnumerable typeNames, IScopedNode input, ValidationState state) { return valueTypes.Where(t => !typeNames.Contains(t)) .Select(t => new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INCORRECT, $"Type of the {propertyName} property '{t}' doesn't match with the type(s) of the element '{string.Join(',', typeNames)}'").AsResult(state)); diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs index fb81a9f3..65e51e2d 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs @@ -12,18 +12,18 @@ namespace Firely.Fhir.Validation /// An that represents a FHIR StructureDefinition /// [DataContract] - public class StructureDefinitionValidator : IValidatable + internal class StructureDefinitionValidator : IValidatable { /// public JToken ToJson() => new JProperty("elementDefinition", new JObject()); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { //this can be expanded with other validate functionality var evidence = validateInvariantUniqueness(input, state); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } /// @@ -33,7 +33,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati /// /// /// - private static List validateInvariantUniqueness(ITypedElement input, ValidationState state) + private static List validateInvariantUniqueness(IScopedNode input, ValidationState state) { var snapshotElements = input.Children("snapshot").SelectMany(c => c.Children("element")); var diffElements = input.Children("differential").SelectMany(c => c.Children("element")); @@ -44,16 +44,16 @@ private static List validateInvariantUniqueness(ITypedElement inpu return snapshotEvidence.Concat(diffEvidence).Select(i => i.AsResult(state)).ToList(); } - private static List validateInvariantUniqueness(IEnumerable elements) + private static List validateInvariantUniqueness(IEnumerable elements) { //Selects the combination of key and elementDefintion path for the duplicate keys where the paths are not also the same. IEnumerable<(string Key, string Path)> PathsPerInvariantKey = elements .SelectMany(e => e.Children("constraint") .Select(c => (Key: c.Children("key") - .Single().Value.ToString(), + .Single().Value.ToString()!, Path: e.Children("path") - .Single().Value.ToString()))); + .Single().Value.ToString()!))); IEnumerable<(string Key, IEnumerable Paths)> PathsPerDuplicateInvariantKey = PathsPerInvariantKey.GroupBy(pair => pair.Key) .Select(group => (Key: group.Key, Paths: group.Select(pair => pair.Path) // select all paths, per invariant key diff --git a/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj b/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj index 7d78efc1..2510e5a9 100644 --- a/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj +++ b/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj @@ -1,19 +1,28 @@ - + - + - - Firely validation library - Next-gen Firely validation library for FHIR data. - HL7;FHIR;Validation;Utility; - Firely.Fhir.Validation - + + Firely validation library + Next-gen Firely validation library for FHIR data. + HL7;FHIR;Validation;Utility; + Firely.Fhir.Validation + - - - - - - + + + + + + + + + + + + + + + diff --git a/src/Firely.Fhir.Validation/Impl/AllValidator.cs b/src/Firely.Fhir.Validation/Impl/AllValidator.cs index e0fc2187..9c4eebee 100644 --- a/src/Firely.Fhir.Validation/Impl/AllValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AllValidator.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// An assertion that expresses that all member assertions should hold. /// [DataContract] - public class AllValidator : IGroupValidatable + internal class AllValidator : IGroupValidatable { /// /// The member assertions the instance should be validated against. @@ -65,9 +65,9 @@ public AllValidator(bool shortcircuitEvaluation, params IAssertion[] members) : { } - /// + /// public ResultReport Validate( - IEnumerable input, + IEnumerable input, ValidationContext vc, ValidationState state) { @@ -80,16 +80,16 @@ public ResultReport Validate( evidence.Add(result); if (!result.IsSuccessful) break; } - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } else return - ResultReport.FromEvidence(Members + ResultReport.Combine(Members .Select(ma => ma.ValidateMany(input, vc, state)).ToList()); } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); /// diff --git a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs index 863cbe1b..154de6f1 100644 --- a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// An assertion that expresses that any of its member assertions should hold. /// [DataContract] - public class AnyValidator : IGroupValidatable + internal class AnyValidator : IGroupValidatable { /// /// The member assertions of which at least one should hold. @@ -48,9 +48,9 @@ public AnyValidator(params IAssertion[] members) : this(members.AsEnumerable(), { } - /// + /// public ResultReport Validate( - IEnumerable input, + IEnumerable input, ValidationContext vc, ValidationState state) { @@ -78,11 +78,11 @@ public ResultReport Validate( if (SummaryError is not null) result.Insert(0, SummaryError.ValidateMany(input, vc, state)); - return ResultReport.FromEvidence(result); + return ResultReport.Combine(result); } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); /// diff --git a/src/Firely.Fhir.Validation/Impl/BasicValidator.cs b/src/Firely.Fhir.Validation/Impl/BasicValidator.cs index 467bae59..33e4f04e 100644 --- a/src/Firely.Fhir.Validation/Impl/BasicValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BasicValidator.cs @@ -12,13 +12,13 @@ namespace Firely.Fhir.Validation /// /// Base class for simple validators that have only a single property to configure. /// - public abstract class BasicValidator : IValidatable + internal abstract class BasicValidator : IValidatable { /// public virtual JToken ToJson() => new JProperty(Key, Value); /// - public abstract ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state); + public abstract ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state); /// /// The name of the property used in the json serialization for this validator." diff --git a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs index 62a6501c..f5a93499 100644 --- a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs @@ -24,7 +24,7 @@ namespace Firely.Fhir.Validation /// An assertion that expresses terminology binding requirements for a coded element. /// [DataContract] - public class BindingValidator : IValidatable + internal class BindingValidator : IValidatable { /// /// How strongly use of the valueset specified in the binding is encouraged or enforced. @@ -102,7 +102,7 @@ public BindingValidator(Canonical valueSetUri, BindingStrength? strength, bool a } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState s) { if (input is null) throw Error.ArgumentNull(nameof(input)); if (input.InstanceType is null) throw Error.Argument(nameof(input), "Binding validation requires input to have an instance type."); @@ -142,7 +142,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati /// Validates whether the instance has the minimum required coded content, depending on the binding. /// /// Will throw an InvalidOperationException when the input is not of a bindeable type. - private ResultReport verifyContentRequirements(ITypedElement source, Element bindable, ValidationState s) + private ResultReport verifyContentRequirements(IScopedNode source, Element bindable, ValidationState s) { switch (bindable) { @@ -166,7 +166,7 @@ private static bool codeableConceptHasCode(CodeableConcept cc) => cc.Coding.Any(cd => !string.IsNullOrEmpty(cd.Code)); - private ResultReport validateCode(ITypedElement source, Element bindable, ValidationContext vc, ValidationState s) + private ResultReport validateCode(IScopedNode source, Element bindable, ValidationContext vc, ValidationState s) { //EK 20170605 - disabled inclusion of warnings/errors for all but required bindings since this will // 1) create superfluous messages (both saying the code is not valid) coming from the validateResult + the outcome.AddIssue() @@ -262,13 +262,13 @@ private static (Issue?, string?) callService(ValidateCodeParameters parameters, catch (FhirOperationException tse) { var desiredResult = ctx.HandleValidateCodeServiceFailure?.Invoke(parameters, tse) - ?? ValidationContext.TerminologyServiceExceptionResult.Warning; + ?? TerminologyServiceExceptionResult.Warning; var message = $"Terminology service failed while validating {display}: {tse.Message}"; return desiredResult switch { - ValidationContext.TerminologyServiceExceptionResult.Error => (Issue.TERMINOLOGY_OUTPUT_ERROR, message), - ValidationContext.TerminologyServiceExceptionResult.Warning => (Issue.TERMINOLOGY_OUTPUT_WARNING, message), + TerminologyServiceExceptionResult.Error => (Issue.TERMINOLOGY_OUTPUT_ERROR, message), + TerminologyServiceExceptionResult.Warning => (Issue.TERMINOLOGY_OUTPUT_WARNING, message), _ => throw new NotSupportedException("Logic error: unknown terminology service exception result.") }; } diff --git a/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs b/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs index 647d0698..f7d6abae 100644 --- a/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs @@ -11,13 +11,13 @@ namespace Firely.Fhir.Validation /// The purpose of this validator is to enforce this rule and perform the necessary checks. /// [DataContract] - public class CanonicalValidator : IValidatable + internal class CanonicalValidator : IValidatable { /// public JToken ToJson() => new JProperty("canonical", new JObject()); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { switch (input.Value) { diff --git a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs index 3ad38508..afb55653 100644 --- a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs @@ -6,7 +6,6 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Support; -using Hl7.Fhir.Validation; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; @@ -22,7 +21,7 @@ namespace Firely.Fhir.Validation /// of elements in a slice for example. /// [DataContract] - public class CardinalityValidator : IGroupValidatable + internal class CardinalityValidator : IGroupValidatable { /// /// Lower bound for the cardinality. If not set, there is no lower bound. @@ -75,7 +74,7 @@ string other when int.TryParse(other, out var maximum) => maximum, } /// - public ResultReport Validate(IEnumerable input, ValidationContext _, ValidationState s) + public ResultReport Validate(IEnumerable input, ValidationContext _, ValidationState s) { var count = input.Count(); return buildResult(count, s); @@ -87,7 +86,7 @@ private ResultReport buildResult(int count, ValidationState s) => !inRange(count : ResultReport.SUCCESS; /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => buildResult(1, state); private bool inRange(int x) => (!Min.HasValue || x >= Min.Value) && (!Max.HasValue || x <= Max.Value); diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index af1be4f2..c6c0ad58 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -9,6 +9,7 @@ using Hl7.Fhir.Utility; using Newtonsoft.Json.Linq; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; @@ -19,7 +20,7 @@ namespace Firely.Fhir.Validation /// can be applied to a child, depending on its name. /// [DataContract] - public class ChildrenValidator : IValidatable, IReadOnlyDictionary + internal class ChildrenValidator : IValidatable, IReadOnlyDictionary { private readonly Dictionary _childList = new(); @@ -76,7 +77,7 @@ public JToken ToJson() => new JProperty(child.Key, child.Value.ToJson().MakeNestedProp())) }); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { var evidence = new List(); @@ -89,7 +90,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati elementsToMatch.Insert(0, new ValueElementNode(input)); var matchResult = ChildNameMatcher.Match(ChildList, elementsToMatch); - if (matchResult.UnmatchedInstanceElements.Any() && !AllowAdditionalChildren) + if (matchResult.UnmatchedInstanceElements?.Count > 0 && !AllowAdditionalChildren) { var elementList = string.Join(",", matchResult.UnmatchedInstanceElements.Select(e => $"'{e.Name}'")); evidence.Add(new IssueAssertion(Issue.CONTENT_ELEMENT_HAS_UNKNOWN_CHILDREN, $"Encountered unknown child elements {elementList}") @@ -97,28 +98,28 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati } evidence.AddRange( - matchResult.Matches.Select(m => + matchResult.Matches?.Select(m => m.Assertion.ValidateMany( m.InstanceElements ?? NOELEMENTS, vc, state .UpdateLocation(vs => vs.ToChild(m.ChildName)) .UpdateInstanceLocation(ip => ip.ToChild(m.ChildName, choiceElement(m))) - ))); + )) ?? Enumerable.Empty()); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); - static string? choiceElement(Match m) => m.ChildName.EndsWith("[x]") ? m.InstanceElements?.FirstOrDefault().InstanceType : null; + static string? choiceElement(Match m) => m.ChildName.EndsWith("[x]") ? m.InstanceElements?.FirstOrDefault()?.InstanceType : null; } - private static readonly List NOELEMENTS = new(); + private static readonly List NOELEMENTS = new(); #region IDictionary implementation /// public bool ContainsKey(string key) => _childList.ContainsKey(key); /// - public bool TryGetValue(string key, out IAssertion value) => _childList.TryGetValue(key, out value); + public bool TryGetValue(string key, [MaybeNullWhen(false)] out IAssertion value) => _childList.TryGetValue(key, out value); /// public IEnumerator> GetEnumerator() => ((IEnumerable>)_childList).GetEnumerator(); @@ -146,7 +147,7 @@ public IAssertion this[string key] internal class ChildNameMatcher { - public static MatchResult Match(IReadOnlyDictionary assertions, IEnumerable children) + public static MatchResult Match(IReadOnlyDictionary assertions, IEnumerable children) { var elementsToMatch = children.ToList(); @@ -172,7 +173,7 @@ public static MatchResult Match(IReadOnlyDictionary assertio return new(matches, elementsToMatch.ToList()); } - private static bool nameMatches(string name, ITypedElement instanceElement) + private static bool nameMatches(string name, IScopedNode instanceElement) { var definedName = name; @@ -197,7 +198,7 @@ private static bool nameMatches(string name, ITypedElement instanceElement) /// The list of children that matched an element in the definition of the type. /// The list of children that could not be matched against the defined list of children in the definition /// of the type. - internal record MatchResult(List? Matches, List? UnmatchedInstanceElements); + internal record MatchResult(List? Matches, List? UnmatchedInstanceElements); /// /// This is a pair that corresponds to a set of elements that needs to be validated against an assertion. @@ -207,5 +208,5 @@ internal record MatchResult(List? Matches, List? Unmatched /// Set of elements belong to this child /// Usually, this is the set of elements with the same name and the group of assertions that represents /// the validation rule for that element generated from the StructureDefinition. - internal record Match(string ChildName, IAssertion Assertion, List? InstanceElements = null); + internal record Match(string ChildName, IAssertion Assertion, List? InstanceElements = null); } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs index d3e57952..208e167e 100644 --- a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// An that represents a FHIR datatype (except Extension). /// [DataContract] - public class DatatypeSchema : FhirSchema + internal class DatatypeSchema : FhirSchema { /// /// Constructs a new @@ -35,16 +35,16 @@ public DatatypeSchema(StructureDefinitionInformation structureDefinition, IEnume } /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { // Schemas representing the root of a FHIR datatype cannot meaningfully be used as a GroupValidatable, // so we'll turn this into a normal IValidatable. var results = input.Select((i, index) => Validate(i, vc, state.UpdateInstanceLocation(d => d.ToIndex(index)))); - return ResultReport.FromEvidence(results.ToList()); + return ResultReport.Combine(results.ToList()); } /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { // FHIR specific rule about dealing with abstract datatypes (not profiles!): if this schema is an abstract datatype, // we need to run validation against the schema for the actual type, not the abstract type. diff --git a/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs b/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs index 411121c9..452750e5 100644 --- a/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs @@ -23,7 +23,7 @@ namespace Firely.Fhir.Validation /// string of the anchor. /// [DataContract] - public class DefinitionsAssertion : IAssertion + internal class DefinitionsAssertion : IAssertion { /// /// The list of subschemas. @@ -49,7 +49,7 @@ public DefinitionsAssertion(IEnumerable schemas) /// Find the first subschema with the given anchor. /// /// An if found, otherwise null. - public ElementSchema FindFirstByAnchor(string anchor) => + public ElementSchema? FindFirstByAnchor(string anchor) => Schemas.FirstOrDefault(s => s.Id == "#" + anchor); /// diff --git a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs index c74d4fad..5fa16c91 100644 --- a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// schema to be succesful. /// [DataContract] - public class ElementSchema : IGroupValidatable + internal class ElementSchema : IGroupValidatable { /// /// The unique id for this schema. @@ -70,45 +70,45 @@ private static IReadOnlyCollection extractShortcutMembers(IEnumerabl => members.OfType().ToList(); - /// + /// public virtual ResultReport Validate( - IEnumerable input, + IEnumerable input, ValidationContext vc, ValidationState state) { // If there is no input, just run the cardinality checks, nothing else - essential to keep validation performance high. if (!input.Any()) { - var nothing = Enumerable.Empty(); + var nothing = Enumerable.Empty(); if (!CardinalityValidators.Any()) return ResultReport.SUCCESS; else { var validationResults = CardinalityValidators.Select(cv => cv.Validate(nothing, vc, state)).ToList(); - return ResultReport.FromEvidence(validationResults); + return ResultReport.Combine(validationResults); } } var members = Members.Where(vc.Filter); var subresult = members.Select(ma => ma.ValidateMany(input, vc, state)); - return ResultReport.FromEvidence(subresult.ToList()); + return ResultReport.Combine(subresult.ToList()); } /// - public virtual ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public virtual ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { // If we have shortcut members, run them first if (ShortcutMembers.Any()) { var subResult = ShortcutMembers.Where(vc.Filter).Select(ma => ma.ValidateOne(input, vc, state)); - var report = ResultReport.FromEvidence(subResult.ToList()); + var report = ResultReport.Combine(subResult.ToList()); if (!report.IsSuccessful) return report; } var members = Members.Where(vc.Filter); var subresult = members.Select(ma => ma.ValidateOne(input, vc, state)); - return ResultReport.FromEvidence(subresult.ToList()); + return ResultReport.Combine(subresult.ToList()); } /// @@ -155,7 +155,7 @@ static JToken nest(JToken mem) => /// Find the first subschema with the given anchor. /// /// An if found, otherwise null. - public ElementSchema FindFirstByAnchor(string anchor) => + public ElementSchema? FindFirstByAnchor(string anchor) => Members.OfType().Select(da => da.FindFirstByAnchor(anchor)).FirstOrDefault(s => s is not null); /// diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs index d510dc50..fa3174cf 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -using static Firely.Fhir.Validation.ValidationContext; namespace Firely.Fhir.Validation { @@ -18,7 +17,7 @@ namespace Firely.Fhir.Validation /// A schema representing a FHIR Extension datatype. /// [DataContract] - public class ExtensionSchema : FhirSchema + internal class ExtensionSchema : FhirSchema { /// /// Constructs a new @@ -39,7 +38,7 @@ public ExtensionSchema(StructureDefinitionInformation structureDefinition, IEnum /// /// Gets the canonical of the profile referred to in the url property of the extension. /// - public static Canonical? GetExtensionUri(ITypedElement instance) => + public static Canonical? GetExtensionUri(IScopedNode instance) => instance .Children("url") .Select(ite => ite.Value) @@ -49,7 +48,7 @@ public ExtensionSchema(StructureDefinitionInformation structureDefinition, IEnum .FirstOrDefault(); // this will actually always be max one, but that's validated by a cardinality validator. /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { // Group the instances by their url - this allows a IGroupValidatable schema for the // extension to validate the "extension cardinality". @@ -117,7 +116,7 @@ public override ResultReport Validate(IEnumerable input, Validati } } - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); static ExtensionUrlFollower callback(ExtensionUrlFollower? follower) => follower ?? ((l, c) => ExtensionUrlHandling.WarnIfMissing); @@ -127,12 +126,12 @@ static ExtensionUrlFollower callback(ExtensionUrlFollower? follower) => /// This invokes the actual validation for an Extension schema, without the special magic of /// fetching the url, so this is the "normal" schema validation. /// - protected ResultReport ValidateExtensionSchema(IEnumerable input, + protected ResultReport ValidateExtensionSchema(IEnumerable input, ValidationContext vc, ValidationState state) => base.Validate(input, vc, state); /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); diff --git a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs index bfff04d2..fe720381 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ele-1". /// [DataContract] - public class FhirEle1Validator : InvariantValidator + internal class FhirEle1Validator : InvariantValidator { /// public override string Key => "ele-1"; @@ -31,7 +31,7 @@ public class FhirEle1Validator : InvariantValidator public override string? HumanDescription => "All FHIR elements must have a @value or children"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { // Original R4B expression: "expression": "hasValue() or (children().count() > id.count()) or $this is Parameters", diff --git a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs index afcaef6c..e9e01a7e 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ext-1". /// [DataContract] - public class FhirExt1Validator : InvariantValidator + internal class FhirExt1Validator : InvariantValidator { /// public override string Key => "ext-1"; @@ -30,7 +30,7 @@ public class FhirExt1Validator : InvariantValidator public override string? HumanDescription => "Must have either extensions or value[x], not both"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { // Original expression: "expression": "extension.exists() != value.exists()", diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 47ddb012..25742747 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -10,7 +10,6 @@ using Hl7.Fhir.Specification.Terminology; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using Hl7.FhirPath; using Hl7.FhirPath.Expressions; using Newtonsoft.Json.Linq; @@ -25,7 +24,7 @@ namespace Firely.Fhir.Validation /// An assertion expressed using FhirPath. /// [DataContract] - public class FhirPathValidator : InvariantValidator + internal class FhirPathValidator : InvariantValidator { /// [DataMember] @@ -97,11 +96,13 @@ public override JToken ToJson() } /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState s) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState s) { try { - var node = input as ScopedNode ?? new ScopedNode(input); +#pragma warning disable CS0618 // Type or member is obsolete + var node = input as ScopedNode ?? new ScopedNode(input.AsTypedElement()); +#pragma warning restore CS0618 // Type or member is obsolete var context = new FhirEvaluationContext(node.ResourceContext) { TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService) @@ -152,12 +153,14 @@ private CompiledExpression getDefaultCompiledExpression(FhirPathCompiler compile } } - private bool predicate(ITypedElement input, EvaluationContext context, ValidationContext vc) + private bool predicate(IScopedNode input, EvaluationContext context, ValidationContext vc) { var compiler = vc?.FhirPathCompiler ?? DefaultCompiler; var compiledExpression = getDefaultCompiledExpression(compiler); - return compiledExpression.IsTrue(input, context); +#pragma warning disable CS0618 // Type or member is obsolete + return compiledExpression.IsTrue(input.AsTypedElement(), context); +#pragma warning restore CS0618 // Type or member is obsolete } /// diff --git a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs index 3deff3d1..18dbffa8 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// /// An that represents a FHIR datatype or resource. /// - public abstract class FhirSchema : ElementSchema + internal abstract class FhirSchema : ElementSchema { /// /// A collection of information from the StructureDefintion from which @@ -42,7 +42,7 @@ public FhirSchema(StructureDefinitionInformation structureDefinition, IEnumerabl } /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { state = state .UpdateLocation(sp => sp.InvokeSchema(this)) @@ -51,7 +51,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext vc, } /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { state = state.UpdateLocation(sp => sp.InvokeSchema(this)); return base.Validate(input, vc, state); diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs index 5cc89663..17a5a4bf 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ext-1". /// [DataContract] - public class FhirTxt1Validator : InvariantValidator + internal class FhirTxt1Validator : InvariantValidator { /// public override string Key => "txt-1"; @@ -32,7 +32,7 @@ public class FhirTxt1Validator : InvariantValidator public override string? HumanDescription => "The narrative SHALL contain only the basic html formatting elements and attributes described in chapters 7-11 (except section 4 of chapter 9) and 15 of the HTML 4.0 standard, elements (either name or href), images and internally contained style attributes"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { // Original expression: "expression": "htmlChecks()" @@ -42,7 +42,7 @@ protected override (bool, ResultReport?) RunInvariant(ITypedElement input, Valid { case string value: { - var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString(), out var errors); + var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString()!, out var errors); if (result) { @@ -55,11 +55,11 @@ protected override (bool, ResultReport?) RunInvariant(ITypedElement input, Valid } } default: - return (false, new ResultReport(ValidationResult.Failure, + return (false, new ResultReport(ValidationResult.Failure, new IssueAssertion(Issue.CONTENT_ELEMENT_INVALID_PRIMITIVE_VALUE, $"Narrative should be of type string, but is of type ({input.Value.GetType()})"))); - } + } } /// diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs index 47ccd9aa..60bb9cbf 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ext-1". /// [DataContract] - public class FhirTxt2Validator : InvariantValidator + internal class FhirTxt2Validator : InvariantValidator { /// public override string Key => "txt-2"; @@ -30,7 +30,7 @@ public class FhirTxt2Validator : InvariantValidator public override string? HumanDescription => "The narrative SHALL have some non-whitespace content"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { //Check whether the narrative contains non-whitespace content. return (!string.IsNullOrWhiteSpace(input.Value.ToString()), null); diff --git a/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs index 96556195..ca15527f 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs @@ -13,9 +13,9 @@ namespace Firely.Fhir.Validation /// /// Assertion about the stated instance type of an element. /// - /// The instance type is taken from + /// The instance type is taken from [DataContract] - public class FhirTypeLabelValidator : BasicValidator + internal class FhirTypeLabelValidator : BasicValidator { /// /// The stated instance type. @@ -39,7 +39,7 @@ public FhirTypeLabelValidator(string label) protected override object Value => Label; /// - public override ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public override ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { var result = input.InstanceType == Label ? ResultReport.SUCCESS : diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index d93cf91e..f49beb48 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -5,6 +5,7 @@ */ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; using Hl7.Fhir.Support; using Newtonsoft.Json.Linq; @@ -18,47 +19,55 @@ namespace Firely.Fhir.Validation /// Asserts that the value of an element is exactly the same as a given fixed value. /// [DataContract] - public class FixedValidator : IValidatable + internal class FixedValidator : IValidatable { + private readonly JToken _fixedJToken; + /// - /// The fixed value to compare an instance against. + /// The fixed value to compare against. /// [DataMember] - public ITypedElement FixedValue { get; private set; } + public DataType FixedValue { get; } /// - /// Initializes a new FixedValidator given the fixed value. + /// Initializes a new FixedValidator given a (primitive) .NET value. /// - public FixedValidator(ITypedElement fixedValue) + public FixedValidator(DataType fixedValue) { FixedValue = fixedValue ?? throw new ArgumentNullException(nameof(fixedValue)); + _fixedJToken = FixedValue.ToJToken(); } - /// - /// Initializes a new FixedValidator given a (primitive) .NET value. - /// - /// The .NET primitive will be turned into a based - /// fixed value using , so this constructor - /// supports any conversion done there. - public FixedValidator(object fixedValue) : this(ElementNode.ForPrimitive(fixedValue)) { } - /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - if (!input.IsExactlyEqualTo(FixedValue, ignoreOrder: true)) + var fixedValue = FixedValue.ToScopedNode(); + if (!input.IsExactlyEqualTo(fixedValue, ignoreOrder: true)) { return new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, - $"Value '{displayValue(input)}' is not exactly equal to fixed value '{displayValue(FixedValue)}'") + $"Value '{displayValue(input)}' is not exactly equal to fixed value '{displayJToken(_fixedJToken)}'") .AsResult(s); } return ResultReport.SUCCESS; - static string displayValue(ITypedElement te) => - te.Children().Any() ? te.ToJson() : te.Value.ToString(); + static string displayValue(IScopedNode te) => te.Children().Any() ? ToJson(te) : te.Value.ToString()!; + + static string displayJToken(JToken jToken) => + jToken is JValue val + ? val.ToString() + : jToken.ToString(Newtonsoft.Json.Formatting.None); + + static string ToJson(IScopedNode instance) + { +#pragma warning disable CS0618 // Type or member is obsolete + var node = instance.AsTypedElement(); +#pragma warning restore CS0618 // Type or member is obsolete + return node.ToJObject().ToString(Newtonsoft.Json.Formatting.None); + } } /// - public JToken ToJson() => new JProperty($"Fixed[{FixedValue.InstanceType}]", FixedValue.ToPropValue()); + public JToken ToJson() => new JProperty($"Fixed[{FixedValue.TypeName}]", _fixedJToken); } } diff --git a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs index 39a2fad2..6f99d3ee 100644 --- a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// An assertion expressed using FhirPath. /// [DataContract] - public abstract class InvariantValidator : IValidatable + internal abstract class InvariantValidator : IValidatable { /// /// The shorthand code identifying the invariant, as defined in the StructureDefinition. @@ -52,10 +52,10 @@ public abstract class InvariantValidator : IValidatable /// /// Implements the logic for running the invariant. /// - protected abstract (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState s); + protected abstract (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState s); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState s) { var (success, directAssertion) = RunInvariant(input, vc, s); diff --git a/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs b/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs index ebede144..27bf3aea 100644 --- a/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs @@ -69,7 +69,7 @@ public class IssueAssertion : IFixedResult, IValidatable, IEquatable /// A reference to the definition (=an element in a StructureDefinition) that raised the issue. /// - public DefinitionPath? DefinitionPath { get; private set; } + internal DefinitionPath? DefinitionPath { get; private set; } /// /// Interprets the of the assertion as a @@ -143,7 +143,7 @@ public JToken ToJson() public static class Pattern { /// - /// Will be replaced by at runtime. + /// Will be replaced by at runtime. /// public const string INSTANCETYPE = "%INSTANCETYPE%"; @@ -154,7 +154,7 @@ public static class Pattern } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState state) + ResultReport IValidatable.Validate(IScopedNode input, ValidationContext _, ValidationState state) { // Validation does not mean anything more than using this instance as a prototype and // turning the issue assertion into a result by cloning the prototype and setting the @@ -172,7 +172,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext _, Validatio /// /// /// - public ResultReport AsResult(ValidationState state) => asResult(state.Location.InstanceLocation.ToString(), state.Location.DefinitionPath); + internal ResultReport AsResult(ValidationState state) => asResult(state.Location.InstanceLocation.ToString(), state.Location.DefinitionPath); /// /// Package this as a diff --git a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs index fe2e70ee..d9417e97 100644 --- a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs @@ -8,7 +8,6 @@ using Hl7.Fhir.ElementModel.Types; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System.Runtime.Serialization; namespace Firely.Fhir.Validation @@ -17,7 +16,7 @@ namespace Firely.Fhir.Validation /// Asserts a maximum length on an element that contains a string value. /// [DataContract] - public class MaxLengthValidator : BasicValidator + internal class MaxLengthValidator : BasicValidator { /// /// The maximum length the string in the instance should be. @@ -44,7 +43,7 @@ public MaxLengthValidator(int maximumLength) protected override object Value => MaximumLength; /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState s) + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState s) { if (input == null) throw Error.ArgumentNull(nameof(input)); diff --git a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs index 4367ceba..6f9c31a9 100644 --- a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs @@ -8,7 +8,6 @@ using Hl7.Fhir.ElementModel.Types; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using Newtonsoft.Json.Linq; using System; using System.Runtime.Serialization; @@ -19,7 +18,7 @@ namespace Firely.Fhir.Validation /// Asserts the maximum (or minimum) value for an element. /// [DataContract] - public class MinMaxValueValidator : IValidatable + internal class MinMaxValueValidator : IValidatable { /// /// Represents a mode op operation for the . @@ -87,7 +86,7 @@ public MinMaxValueValidator(ITypedElement limit, ValidationMode minMaxType) public MinMaxValueValidator(long limit, ValidationMode minMaxType) : this(ElementNode.ForPrimitive(limit), minMaxType) { } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { if (!Any.TryConvert(input.Value, out var instanceValue)) { diff --git a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs index bf4d79b7..39c9f8f1 100644 --- a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs @@ -9,6 +9,7 @@ using Hl7.FhirPath; using Hl7.FhirPath.Expressions; using Newtonsoft.Json.Linq; +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -20,7 +21,7 @@ namespace Firely.Fhir.Validation /// Used internally only for discriminating the cases of a . /// [DataContract] - public class PathSelectorValidator : IValidatable + internal class PathSelectorValidator : IValidatable { /// /// The FhirPath statement used to select a value to validate. @@ -47,11 +48,13 @@ public PathSelectorValidator(string path, IAssertion other) /// Note that this validator is only used internally to represent the checks for /// the path-based discriminated cases in a , so this validator /// does not produce standard Issue-based errors. - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { initializeFhirPathCache(vc, state); - var selected = state.Global.FPCompilerCache!.Select(input, Path).ToList(); +#pragma warning disable CS0618 // Type or member is obsolete + var selected = state.Global.FPCompilerCache!.Select(input.AsTypedElement(), Path).ToList(); +#pragma warning restore CS0618 // Type or member is obsolete if (selected.Any()) { @@ -60,17 +63,19 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati state = state.UpdateInstanceLocation(ip => ip.AddInternalReference(selected.First().Location)); } - return selected switch + var selectedScopedNodes = cast(selected); + + return selectedScopedNodes switch { // 0, 1 or more results are ok for group validatables. Even an empty result is valid for, say, cardinality constraints. - _ when Other is IGroupValidatable igv => igv.Validate(selected, vc, state), + _ when Other is IGroupValidatable igv => igv.Validate(selectedScopedNodes, vc, state), // A non-group validatable cannot be used with 0 results. { Count: 0 } => new ResultReport(ValidationResult.Failure, new TraceAssertion(state.Location.InstanceLocation.ToString(), $"The FhirPath selector {Path} did not return any results.")), // 1 is ok for non group validatables - { Count: 1 } => Other.ValidateMany(selected, vc, state), + { Count: 1 } => Other.ValidateMany(selectedScopedNodes, vc, state), // Otherwise we have too many results for a non-group validatable. _ => new ResultReport(ValidationResult.Failure, @@ -86,6 +91,8 @@ static void initializeFhirPathCache(ValidationContext vc, ValidationState state) state.Global.FPCompilerCache = new FhirPathCompilerCache(compiler); } } + + List cast(List elements) => elements.Select(e => e.AsScopedNode()).ToList(); } /// diff --git a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs index 5274f2a6..d607d392 100644 --- a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs @@ -5,7 +5,7 @@ */ using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Serialization; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Newtonsoft.Json.Linq; using System; @@ -21,42 +21,43 @@ namespace Firely.Fhir.Validation /// pattern element /// in the FHIR specification. [DataContract] - public class PatternValidator : IValidatable + internal class PatternValidator : IValidatable { + private readonly JToken _patternJToken; + /// - /// The pattern the instance will be validated against. + /// The pattern value to compare against. /// [DataMember] - public ITypedElement PatternValue { get; private set; } + public DataType PatternValue { get; } /// - /// Initializes a new PatternValidator given a pattern. + /// Initializes a new PatternValidator given a pattern using a (primitive) .NET value. /// - public PatternValidator(ITypedElement patternValue) + public PatternValidator(DataType patternValue) { PatternValue = patternValue ?? throw new ArgumentNullException(nameof(patternValue)); + _patternJToken = PatternValue.ToJToken(); } - /// - /// Initializes a new PatternValidator given a pattern using a (primitive) .NET value. - /// - /// The .NET primitive will be turned into a based - /// pattern using , so this constructor - /// supports any conversion done there. - public PatternValidator(object patternPrimitive) : this(ElementNode.ForPrimitive(patternPrimitive)) { } - /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - var result = input.Matches(PatternValue) + var patternValue = PatternValue.ToScopedNode(); + var result = input.Matches(patternValue) ? ResultReport.SUCCESS - : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{PatternValue.ToJson()}") + : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{displayJToken(_patternJToken)}") // TODO: add value to message .AsResult(s); return result; + + static string displayJToken(JToken jToken) => + jToken is JValue val + ? val.ToString() + : jToken.ToString(Newtonsoft.Json.Formatting.None); } /// - public JToken ToJson() => new JProperty($"pattern[{PatternValue.InstanceType}]", PatternValue.ToPropValue()); + public JToken ToJson() => new JProperty($"pattern[{PatternValue.TypeName}]", _patternJToken); } } diff --git a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs index 61c7cba0..6cf9bc99 100644 --- a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation /// to be found at runtime in the "reference" child of the input. /// [DataContract] - public class ReferencedInstanceValidator : IValidatable + internal class ReferencedInstanceValidator : IValidatable { /// /// When the referenced resource was found, it will be validated against @@ -58,8 +58,8 @@ public ReferencedInstanceValidator(IAssertion schema, /// public bool HasAggregation => AggregationRules?.Any() ?? false; - /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + /// + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { if (vc.ElementSchemaResolver is null) throw new ArgumentException($"Cannot validate because {nameof(ValidationContext)} does not contain an ElementSchemaResolver."); @@ -97,7 +97,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati _ => validateReferencedResource(reference, vc, resolution, state) }; - return ResultReport.FromEvidence(evidence.Append(referenceResolutionReport).ToList()); + return ResultReport.Combine(evidence.Append(referenceResolutionReport).ToList()); } else return ResultReport.SUCCESS; @@ -107,10 +107,10 @@ private record ResolutionResult(ITypedElement? ReferencedResource, AggregationMo /// /// Try to fetch the referenced resource. The resource may be present in the instance (bundled, contained) - /// or externally. In the last case, the is used + /// or externally. In the last case, the is used /// to fetch the resource. /// - private (IReadOnlyCollection, ResolutionResult) fetchReference(ITypedElement input, string reference, ValidationContext vc, ValidationState s) + private (IReadOnlyCollection, ResolutionResult) fetchReference(IScopedNode input, string reference, ValidationContext vc, ValidationState s) { ResolutionResult resolution = new(null, null, null); List evidence = new(); @@ -123,7 +123,7 @@ private record ResolutionResult(ITypedElement? ReferencedResource, AggregationMo // Now that we have tried to fetch the reference locally, we have also determined the kind of // reference we are dealing with, so check it for aggregation and versioning rules. - if (HasAggregation && !AggregationRules.Any(a => a == resolution.ReferenceKind)) + if (HasAggregation && AggregationRules?.Any(a => a == resolution.ReferenceKind) == false) { var allowed = string.Join(", ", AggregationRules); evidence.Add(new IssueAssertion(Issue.CONTENT_REFERENCE_OF_INVALID_KIND, @@ -150,7 +150,7 @@ private record ResolutionResult(ITypedElement? ReferencedResource, AggregationMo { try { - var externalReference = vc.ResolveExternalReference!(reference, input.Location); + var externalReference = vc.ResolveExternalReference!(reference, s.Location.InstanceLocation.ToString()); resolution = resolution with { ReferencedResource = externalReference }; } catch (Exception e) @@ -221,14 +221,14 @@ private ResultReport validateReferencedResource(string reference, ValidationCont // references to external entities will operate within a new instance of a validator (and hence a new tracking context). // In both cases, the outcome is included in the result. if (resolution.ReferenceKind != AggregationMode.Referenced) - return Schema.ValidateOne(resolution.ReferencedResource, vc, state.UpdateInstanceLocation(dp => dp.AddInternalReference(resolution.ReferencedResource.Location))); + return Schema.ValidateOne(resolution.ReferencedResource.AsScopedNode(), vc, state.UpdateInstanceLocation(dp => dp.AddInternalReference(resolution.ReferencedResource.Location))); else { //TODO: We're using state to track the external URL, but this actually would be better //implemented on the ScopedNode instead - add this (and combine with FullUrl?) there. var newState = state.NewInstanceScope(); newState.Instance.ResourceUrl = reference; - return Schema.ValidateOne(new ScopedNode(resolution.ReferencedResource), vc, newState); + return Schema.ValidateOne(resolution.ReferencedResource.AsScopedNode(), vc, newState); } } @@ -251,6 +251,6 @@ public JToken ToJson() /// /// Whether this validator recognizes the given type as a reference type. /// - public static bool IsSupportedReferenceType(string typeCode) => typeCode == "Reference"; // || typeCode == "canonical" + public static bool IsSupportedReferenceType(string typeCode) => typeCode is "Reference" or "CodeableReference"; } } diff --git a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs index 5e302659..c0a9cc67 100644 --- a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Asserts that the value of an element (converted to a string) matches a given regular expression. /// [DataContract] - public class RegExValidator : BasicValidator + internal class RegExValidator : BasicValidator { /// /// The regex pattern used to validate the instance against. @@ -44,10 +44,10 @@ public RegExValidator(string pattern) protected override object Value => Pattern; /// - public override ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public override ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { var value = toStringRepresentation(input); - var success = _regex.Match(value).Success; + var success = value is not null && _regex.Match(value).Success; return !success ? new IssueAssertion(Issue.CONTENT_ELEMENT_INVALID_PRIMITIVE_VALUE, $"Value '{value}' does not match regex '{Pattern}'") @@ -55,7 +55,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext _, : ResultReport.SUCCESS; } - private static string? toStringRepresentation(ITypedElement vp) + private static string? toStringRepresentation(IScopedNode vp) { return vp == null || vp.Value == null ? null : diff --git a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs index bb0e62a6..ebb67a00 100644 --- a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// It will perform additional resource-specific validation logic associated with resources, /// like selecting Meta.profile as additional profiles to be validated. [DataContract] - public class ResourceSchema : FhirSchema + internal class ResourceSchema : FhirSchema { /// /// Constructs a new @@ -40,7 +40,7 @@ public ResourceSchema(StructureDefinitionInformation structureDefinition, IEnume /// /// Gets the canonical of the profile(s) referred to in the Meta.profile property of the resource. /// - internal static Canonical[] GetMetaProfileSchemas(ITypedElement instance, ValidationContext.MetaProfileSelector? selector) + internal static Canonical[] GetMetaProfileSchemas(IScopedNode instance, MetaProfileSelector? selector, ValidationState state) { var profiles = instance .Children("meta") @@ -49,23 +49,23 @@ internal static Canonical[] GetMetaProfileSchemas(ITypedElement instance, Valida .OfType() .Select(s => new Canonical(s)); - return callback(selector).Invoke(instance.Location, profiles.ToArray()); + return callback(selector).Invoke(state.Location.InstanceLocation.ToString(), profiles.ToArray()); - static ValidationContext.MetaProfileSelector callback(ValidationContext.MetaProfileSelector? selector) + static MetaProfileSelector callback(MetaProfileSelector? selector) => selector ?? ((_, m) => m); } /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { // Schemas representing the root of a FHIR resource cannot meaningfully be used as a GroupValidatable, // so we'll turn this into a normal IValidatable. var results = input.Select((i, index) => Validate(i, vc, state.UpdateInstanceLocation(d => d.ToIndex(index)))); - return ResultReport.FromEvidence(results.ToList()); + return ResultReport.Combine(results.ToList()); } /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { // FHIR specific rule about dealing with abstract datatypes (not profiles!): if this schema is an abstract datatype, // we need to run validation against the schema for the actual type, not the abstract type. @@ -85,7 +85,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext vc, // FHIR has a few occasions where the schema needs to read into the instance to obtain additional schemas to // validate against (Resource.meta.profile, Extension.url). Fetch these from the instance and combine them into // a coherent set to validate against. - var additionalCanonicals = GetMetaProfileSchemas(input, vc.SelectMetaProfiles); + var additionalCanonicals = GetMetaProfileSchemas(input, vc.SelectMetaProfiles, state); if (additionalCanonicals.Any() && vc.ElementSchemaResolver is null) throw new ArgumentException($"Cannot validate profiles in meta.profile because {nameof(ValidationContext)} does not contain an ElementSchemaResolver."); @@ -104,14 +104,14 @@ public override ResultReport Validate(ITypedElement input, ValidationContext vc, // this should exclude the special fetch magic for Meta.profile (this function) to avoid a loop, so we call the actual validation here. var validationResult = minimalSet.Select(s => s.ValidateResourceSchema(input, vc, state)).ToList(); var validationResultOther = fetchedNonFhirSchemas.Select(s => s.Validate(input, vc, state)).ToList(); - return ResultReport.FromEvidence(fetchErrors.Append(consistencyReport).Concat(validationResult).Concat(validationResultOther).ToArray()); + return ResultReport.Combine(fetchErrors.Append(consistencyReport).Concat(validationResult).Concat(validationResultOther).ToArray()); } /// /// This invokes the actual validation for an resource schema, without the special magic of /// fetching Meta.profile, so this is the "normal" schema validation. /// - protected ResultReport ValidateResourceSchema(ITypedElement input, ValidationContext vc, ValidationState state) + protected ResultReport ValidateResourceSchema(IScopedNode input, ValidationContext vc, ValidationState state) { return state.Global.RunValidations.Start( state, diff --git a/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs b/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs index 52742ccf..966399db 100644 --- a/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// Asserts a validation result. /// [DataContract] - public class ResultAssertion : BasicValidator, IFixedResult + internal class ResultAssertion : BasicValidator, IFixedResult { /// /// Will validate to a success assertion without evidence. @@ -64,7 +64,7 @@ private static ResultReport getResultAssertion(ValidationResult result) => ValidationResult IFixedResult.FixedResult => Result; /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => _fixedReport; + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => _fixedReport; /// public ResultReport AsResult() => _fixedReport; diff --git a/src/Firely.Fhir.Validation/Impl/ResultReport.cs b/src/Firely.Fhir.Validation/Impl/ResultReport.cs index fa4c6376..f6d2a8fc 100644 --- a/src/Firely.Fhir.Validation/Impl/ResultReport.cs +++ b/src/Firely.Fhir.Validation/Impl/ResultReport.cs @@ -47,16 +47,17 @@ public class ResultReport public IReadOnlyList Evidence { get; } /// - /// Creates a new ResultAssertion where the result is derived from the evidence. + /// Creates a new where the result is derived from multiple others + /// reports. /// - /// The evidence is included in the returned result and the total result - /// for the the ResultAssertion is calculated to be the weakest of the evidence. - public static ResultReport FromEvidence(IReadOnlyCollection evidence) + /// All evidence is combined in the returned result and the + /// for the the combined report is determined to be the weakest result of the combined reports. + public static ResultReport Combine(IReadOnlyCollection reports) { - if (evidence.Count == 0) return SUCCESS; - if (evidence.Count == 1) return evidence.Single(); + if (reports.Count == 0) return SUCCESS; + if (reports.Count == 1) return reports.Single(); - var usefulEvidence = evidence.Where(e => !isSuccessWithoutDetails(e)).ToList(); + var usefulEvidence = reports.Where(e => !isSuccessWithoutDetails(e)).ToList(); if (usefulEvidence.Count == 1) return usefulEvidence.Single(); diff --git a/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs b/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs index 7caa1c0f..e3120728 100644 --- a/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Asserts the validity of an element against a fixed schema. /// [DataContract] - public class SchemaReferenceValidator : IGroupValidatable + internal class SchemaReferenceValidator : IGroupValidatable { /// /// A singleton representing a schema reference to . @@ -40,8 +40,8 @@ public SchemaReferenceValidator(Canonical schemaUri) SchemaUri = schemaUri; } - /// - public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + /// + public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { if (vc.ElementSchemaResolver is null) throw new ArgumentException($"Cannot validate because {nameof(ValidationContext)} does not contain an ElementSchemaResolver."); @@ -54,7 +54,7 @@ public ResultReport Validate(IEnumerable input, ValidationContext } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); /// diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index e6f9956a..b19d7a1a 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -22,7 +22,7 @@ namespace Firely.Fhir.Validation /// be validated against the assertions defined for each case. /// [DataContract] - public class SliceValidator : IGroupValidatable + internal class SliceValidator : IGroupValidatable { /// /// Represents a named, conditional assertion on a set of elements. @@ -56,7 +56,7 @@ public class SliceCase /// /// /// - public SliceCase(string name, IAssertion condition, IAssertion assertion) + public SliceCase(string name, IAssertion condition, IAssertion? assertion) { Name = name ?? throw new ArgumentNullException(nameof(name)); Condition = condition ?? throw new ArgumentNullException(nameof(condition)); @@ -122,10 +122,10 @@ public SliceValidator(bool ordered, bool defaultAtEnd, IAssertion @default, IEnu } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); - /// - public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + /// + public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { var lastMatchingSlice = -1; var defaultInUse = false; @@ -192,7 +192,7 @@ public ResultReport Validate(IEnumerable input, ValidationContext evidence.AddRange(buckets.Validate(vc, state)); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } /// @@ -208,7 +208,7 @@ public JToken ToJson() new JProperty("default", def))); } - private record OrderedTypedElement(ITypedElement Node, int Index); + private record OrderedTypedElement(IScopedNode Node, int Index); private class Buckets : Dictionary?> { @@ -226,7 +226,7 @@ public Buckets(IEnumerable slices, IAssertion defaultAssertion) _defaultAssertion = defaultAssertion; } - public void AddToSlice(SliceCase slice, ITypedElement item, int originalIndex) + public void AddToSlice(SliceCase slice, IScopedNode item, int originalIndex) { if (!TryGetValue(slice, out var list)) throw new InvalidOperationException($"Slice should have been initialized with item {slice.Name}."); @@ -235,7 +235,7 @@ public void AddToSlice(SliceCase slice, ITypedElement item, int originalIndex) list.Add(new(item, originalIndex)); } - public void AddToDefault(ITypedElement item, int originalIndex) => _defaultBucket.Add(new(item, originalIndex)); + public void AddToDefault(IScopedNode item, int originalIndex) => _defaultBucket.Add(new(item, originalIndex)); public ResultReport[] Validate(ValidationContext vc, ValidationState state) => this.Select(slice => slice.Key.Assertion.ValidateMany(toListOfTypedElements(slice.Value), vc, forSlice(state, slice.Key.Name, slice.Value))) @@ -246,8 +246,8 @@ private static ValidationState forSlice(ValidationState current, string sliceNam .UpdateLocation(vs => vs.CheckSlice(sliceName)) .UpdateInstanceLocation(vs => vs.AddOriginalIndices(toOrderedList(list))); - private static IEnumerable toListOfTypedElements(IList? list) => - list?.Select(ote => ote.Node) ?? Enumerable.Empty(); + private static IEnumerable toListOfTypedElements(IList? list) => + list?.Select(ote => ote.Node) ?? Enumerable.Empty(); private static IEnumerable toOrderedList(IList? list) => list?.Select(ote => ote.Index) ?? Enumerable.Empty(); diff --git a/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs b/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs index e337e3b2..b635f8b7 100644 --- a/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs @@ -43,7 +43,7 @@ public TraceAssertion(string location, string message) } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState state) + ResultReport IValidatable.Validate(IScopedNode input, ValidationContext _, ValidationState state) { // Validation does not mean anything more than using this instance as a prototype and // turning the trace assertion into a result by cloning the prototype and setting the diff --git a/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs b/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs index 35137495..9ad29ba5 100644 --- a/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs +++ b/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs @@ -12,21 +12,16 @@ [assembly: CLSCompliant(true)] #if DEBUG [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests")] - -#if STU3 -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3")] -#else -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.R4")] -#endif +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R4.Tests")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R5.Tests")] #endif #if RELEASE [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -#if STU3 -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -#else -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.R4, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -#endif +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R4.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R5.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] #endif diff --git a/src/Firely.Fhir.Validation/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..7df5e0df --- /dev/null +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -0,0 +1,122 @@ +#nullable enable +const Firely.Fhir.Validation.IssueAssertion.Pattern.INSTANCETYPE = "%INSTANCETYPE%" -> string! +const Firely.Fhir.Validation.IssueAssertion.Pattern.RESOURCEURL = "%RESOURCEURL%" -> string! +Firely.Fhir.Validation.AssertionToOperationOutcomeExtensions +Firely.Fhir.Validation.Canonical +Firely.Fhir.Validation.Canonical.Anchor.get -> string? +Firely.Fhir.Validation.Canonical.Canonical(Firely.Fhir.Validation.Canonical! original) -> void +Firely.Fhir.Validation.Canonical.Canonical(string! original) -> void +Firely.Fhir.Validation.Canonical.Canonical(string? uri, string? version, string? anchor) -> void +Firely.Fhir.Validation.Canonical.Deconstruct(out string? uri, out string? version, out string? anchor) -> void +Firely.Fhir.Validation.Canonical.HasAnchor.get -> bool +Firely.Fhir.Validation.Canonical.HasVersion.get -> bool +Firely.Fhir.Validation.Canonical.IsAbsolute.get -> bool +Firely.Fhir.Validation.Canonical.Original.get -> string! +Firely.Fhir.Validation.Canonical.ToUri() -> System.Uri! +Firely.Fhir.Validation.Canonical.Uri.get -> string? +Firely.Fhir.Validation.Canonical.Version.get -> string? +Firely.Fhir.Validation.ExtensionUrlFollower +Firely.Fhir.Validation.ExtensionUrlHandling +Firely.Fhir.Validation.ExtensionUrlHandling.DontResolve = 0 -> Firely.Fhir.Validation.ExtensionUrlHandling +Firely.Fhir.Validation.ExtensionUrlHandling.ErrorIfMissing = 2 -> Firely.Fhir.Validation.ExtensionUrlHandling +Firely.Fhir.Validation.ExtensionUrlHandling.WarnIfMissing = 1 -> Firely.Fhir.Validation.ExtensionUrlHandling +Firely.Fhir.Validation.IAssertion +Firely.Fhir.Validation.IExternalReferenceResolver +Firely.Fhir.Validation.IExternalReferenceResolver.ResolveAsync(string! reference) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.IJsonSerializable +Firely.Fhir.Validation.IJsonSerializable.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.InMemoryExternalReferenceResolver +Firely.Fhir.Validation.InMemoryExternalReferenceResolver.InMemoryExternalReferenceResolver() -> void +Firely.Fhir.Validation.InMemoryExternalReferenceResolver.ResolveAsync(string! reference) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.IssueAssertion +Firely.Fhir.Validation.IssueAssertion.AsResult(string! location) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.IssueAssertion.Equals(Firely.Fhir.Validation.IssueAssertion? other) -> bool +Firely.Fhir.Validation.IssueAssertion.IssueAssertion(Hl7.Fhir.Support.Issue! issue, string! message) -> void +Firely.Fhir.Validation.IssueAssertion.IssueAssertion(int issueNumber, string! message, Hl7.Fhir.Model.OperationOutcome.IssueSeverity severity, Hl7.Fhir.Model.OperationOutcome.IssueType? type = null) -> void +Firely.Fhir.Validation.IssueAssertion.IssueNumber.get -> int +Firely.Fhir.Validation.IssueAssertion.Location.get -> string? +Firely.Fhir.Validation.IssueAssertion.Message.get -> string! +Firely.Fhir.Validation.IssueAssertion.Message.set -> void +Firely.Fhir.Validation.IssueAssertion.Pattern +Firely.Fhir.Validation.IssueAssertion.Result.get -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.IssueAssertion.Severity.get -> Hl7.Fhir.Model.OperationOutcome.IssueSeverity +Firely.Fhir.Validation.IssueAssertion.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.IssueAssertion.Type.get -> Hl7.Fhir.Model.OperationOutcome.IssueType? +Firely.Fhir.Validation.MetaProfileSelector +Firely.Fhir.Validation.ResultReport +Firely.Fhir.Validation.ResultReport.Errors.get -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.ResultReport.Evidence.get -> System.Collections.Generic.IReadOnlyList! +Firely.Fhir.Validation.ResultReport.GetIssues(Hl7.Fhir.Model.OperationOutcome.IssueSeverity? severity = null) -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.ResultReport.IsSuccessful.get -> bool +Firely.Fhir.Validation.ResultReport.Result.get -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ResultReport.ResultReport(Firely.Fhir.Validation.ValidationResult result, params Firely.Fhir.Validation.IAssertion![]! evidence) -> void +Firely.Fhir.Validation.ResultReport.ResultReport(Firely.Fhir.Validation.ValidationResult result, System.Collections.Generic.IEnumerable! evidence) -> void +Firely.Fhir.Validation.ResultReport.Warnings.get -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.SchemaResolutionFailedException +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaResolutionFailedException(string! message, Firely.Fhir.Validation.Canonical! schemaUri) -> void +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaResolutionFailedException(string! message, Firely.Fhir.Validation.Canonical! schemaUri, System.Exception! inner) -> void +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaUri.get -> Firely.Fhir.Validation.Canonical! +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaUri.set -> void +Firely.Fhir.Validation.TerminologyServiceExceptionResult +Firely.Fhir.Validation.TerminologyServiceExceptionResult.Error = 1 -> Firely.Fhir.Validation.TerminologyServiceExceptionResult +Firely.Fhir.Validation.TerminologyServiceExceptionResult.Warning = 0 -> Firely.Fhir.Validation.TerminologyServiceExceptionResult +Firely.Fhir.Validation.TraceAssertion +Firely.Fhir.Validation.TraceAssertion.AsResult() -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.TraceAssertion.Equals(Firely.Fhir.Validation.TraceAssertion? other) -> bool +Firely.Fhir.Validation.TraceAssertion.FixedResult.get -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.TraceAssertion.Location.get -> string! +Firely.Fhir.Validation.TraceAssertion.Message.get -> string! +Firely.Fhir.Validation.TraceAssertion.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.TraceAssertion.TraceAssertion(string! location, string! message) -> void +Firely.Fhir.Validation.TypeNameMapper +Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidateBestPracticesSeverity.Error = 1 -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidateBestPracticesSeverity.Warning = 0 -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidateCodeServiceFailureHandler +Firely.Fhir.Validation.ValidationContext +Firely.Fhir.Validation.ValidationContext.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidationContext.ExcludeFilters -> System.Collections.Generic.ICollection!>! +Firely.Fhir.Validation.ValidationContext.FhirPathCompiler -> Hl7.FhirPath.FhirPathCompiler? +Firely.Fhir.Validation.ValidationContext.FollowExtensionUrl -> Firely.Fhir.Validation.ExtensionUrlFollower? +Firely.Fhir.Validation.ValidationContext.HandleValidateCodeServiceFailure -> Firely.Fhir.Validation.ValidateCodeServiceFailureHandler? +Firely.Fhir.Validation.ValidationContext.IncludeFilters -> System.Collections.Generic.ICollection!>! +Firely.Fhir.Validation.ValidationContext.SelectMetaProfiles -> Firely.Fhir.Validation.MetaProfileSelector? +Firely.Fhir.Validation.ValidationContext.TypeNameMapper -> Firely.Fhir.Validation.TypeNameMapper? +Firely.Fhir.Validation.ValidationContext.ValidateCodeService -> Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! +Firely.Fhir.Validation.ValidationContext.ValidationContext() -> void +Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResult.Failure = 1 -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResult.Success = 0 -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResult.Undecided = 2 -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResultExtensions +override Firely.Fhir.Validation.Canonical.Equals(object? obj) -> bool +override Firely.Fhir.Validation.Canonical.GetHashCode() -> int +override Firely.Fhir.Validation.Canonical.ToString() -> string! +override Firely.Fhir.Validation.IssueAssertion.Equals(object? obj) -> bool +override Firely.Fhir.Validation.IssueAssertion.GetHashCode() -> int +override Firely.Fhir.Validation.TraceAssertion.Equals(object? obj) -> bool +override Firely.Fhir.Validation.TraceAssertion.GetHashCode() -> int +static Firely.Fhir.Validation.AssertionToOperationOutcomeExtensions.ToOperationOutcome(this Firely.Fhir.Validation.ResultReport! result) -> Hl7.Fhir.Model.OperationOutcome! +static Firely.Fhir.Validation.Canonical.explicit operator string!(Firely.Fhir.Validation.Canonical! c) -> string! +static Firely.Fhir.Validation.Canonical.ForCoreType(string! type) -> Firely.Fhir.Validation.Canonical! +static Firely.Fhir.Validation.Canonical.implicit operator Firely.Fhir.Validation.Canonical!(string! s) -> Firely.Fhir.Validation.Canonical! +static Firely.Fhir.Validation.Canonical.operator !=(Firely.Fhir.Validation.Canonical? left, Firely.Fhir.Validation.Canonical? right) -> bool +static Firely.Fhir.Validation.Canonical.operator ==(Firely.Fhir.Validation.Canonical? left, Firely.Fhir.Validation.Canonical? right) -> bool +static Firely.Fhir.Validation.IssueAssertion.operator !=(Firely.Fhir.Validation.IssueAssertion? left, Firely.Fhir.Validation.IssueAssertion? right) -> bool +static Firely.Fhir.Validation.IssueAssertion.operator ==(Firely.Fhir.Validation.IssueAssertion? left, Firely.Fhir.Validation.IssueAssertion? right) -> bool +static Firely.Fhir.Validation.ResultReport.Combine(System.Collections.Generic.IReadOnlyCollection! reports) -> Firely.Fhir.Validation.ResultReport! +static Firely.Fhir.Validation.TraceAssertion.operator !=(Firely.Fhir.Validation.TraceAssertion? left, Firely.Fhir.Validation.TraceAssertion? right) -> bool +static Firely.Fhir.Validation.TraceAssertion.operator ==(Firely.Fhir.Validation.TraceAssertion? left, Firely.Fhir.Validation.TraceAssertion? right) -> bool +static Firely.Fhir.Validation.ValidationResultExtensions.Combine(this Firely.Fhir.Validation.ValidationResult a, Firely.Fhir.Validation.ValidationResult b) -> Firely.Fhir.Validation.ValidationResult +static readonly Firely.Fhir.Validation.ResultReport.FAILURE -> Firely.Fhir.Validation.ResultReport! +static readonly Firely.Fhir.Validation.ResultReport.SUCCESS -> Firely.Fhir.Validation.ResultReport! +static readonly Firely.Fhir.Validation.ResultReport.UNDECIDED -> Firely.Fhir.Validation.ResultReport! +static readonly Firely.Fhir.Validation.ValidationContext.DEFAULTEXCLUDEFILTER -> System.Predicate! +virtual Firely.Fhir.Validation.Canonical.$() -> Firely.Fhir.Validation.Canonical! +virtual Firely.Fhir.Validation.Canonical.EqualityContract.get -> System.Type! +virtual Firely.Fhir.Validation.Canonical.Equals(Firely.Fhir.Validation.Canonical? other) -> bool +virtual Firely.Fhir.Validation.Canonical.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Firely.Fhir.Validation.ExtensionUrlFollower.Invoke(string! location, Firely.Fhir.Validation.Canonical? url) -> Firely.Fhir.Validation.ExtensionUrlHandling +virtual Firely.Fhir.Validation.MetaProfileSelector.Invoke(string! location, Firely.Fhir.Validation.Canonical![]! originalProfiles) -> Firely.Fhir.Validation.Canonical![]! +virtual Firely.Fhir.Validation.TypeNameMapper.Invoke(string! local) -> Firely.Fhir.Validation.Canonical? +virtual Firely.Fhir.Validation.ValidateCodeServiceFailureHandler.Invoke(Hl7.Fhir.Specification.Terminology.ValidateCodeParameters! p, Hl7.Fhir.Rest.FhirOperationException! e) -> Firely.Fhir.Validation.TerminologyServiceExceptionResult \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs b/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs index 516030f1..ffdb1701 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs @@ -50,7 +50,7 @@ item.DefinitionPath is not null && item.DefinitionPath.HasDefinitionChoiceInform /// Removes duplicate issues from the . This may happen if an instance is validated against /// profiles that overlap (or where one profile is a base of the other). /// - public static ResultReport RemoveDuplicateEvidence(this ResultReport report) + internal static ResultReport RemoveDuplicateEvidence(this ResultReport report) { var issues = report.Evidence.Distinct().ToList(); // Those assertions for which equivalence is relevant will have implemented IEqualityComparer return new ResultReport(report.Result, issues); @@ -62,7 +62,7 @@ public static ResultReport RemoveDuplicateEvidence(this ResultReport report) /// /// /// - public static ResultReport CleanUp(this ResultReport report) + internal static ResultReport CleanUp(this ResultReport report) { return report.addSliceContextToErrorMessages() .RemoveDuplicateEvidence(); diff --git a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs index 69295de4..695b61ef 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs @@ -19,21 +19,31 @@ namespace Firely.Fhir.Validation /// of that do not implement these two interfaces. These extension /// methods allow the caller to invoke each of them, using a uniform Validate method call. /// - public static class AssertionValidators + internal static class AssertionValidators { + /// + /// Validates a set of instance elements against an assertion. + /// + public static ResultReport Validate(this IAssertion assertion, IEnumerable input, ValidationContext vc) + => assertion.ValidateMany(input, vc, new ValidationState()); + /// /// Validates a set of instance elements against an assertion. /// public static ResultReport Validate(this IAssertion assertion, IEnumerable input, ValidationContext vc) - => assertion.ValidateMany(input.Select(i => i.asScopedNode()), vc, new ValidationState()); + => assertion.ValidateMany(input.Select(i => i.AsScopedNode()), vc, new ValidationState()); /// /// Validates a single instance element against an assertion. /// - public static ResultReport Validate(this IAssertion assertion, ITypedElement input, ValidationContext vc) - => assertion.ValidateOne(input.asScopedNode(), vc, new ValidationState()); + public static ResultReport Validate(this IAssertion assertion, IScopedNode input, ValidationContext vc) + => assertion.ValidateOne(input, vc, new ValidationState()); - private static ITypedElement asScopedNode(this ITypedElement node) => node is ScopedNode ? node : new ScopedNode(node); + /// + /// Validates a single instance element against an assertion. + /// + public static ResultReport Validate(this IAssertion assertion, ITypedElement input, ValidationContext vc) + => assertion.ValidateOne(input.AsScopedNode(), vc, new ValidationState()); /// /// Validates a group of instance elements using an assertion. @@ -41,7 +51,7 @@ public static ResultReport Validate(this IAssertion assertion, ITypedElement inp /// If the assertion is an , this will simply invoke the /// corresponding method on the validator. If not, it will call the validation on the assertion for /// each of the instances in the group and combine the result. - internal static ResultReport ValidateMany(this IAssertion assertion, IEnumerable input, ValidationContext vc, ValidationState state) + internal static ResultReport ValidateMany(this IAssertion assertion, IEnumerable input, ValidationContext vc, ValidationState state) { return assertion switch { @@ -53,14 +63,14 @@ internal static ResultReport ValidateMany(this IAssertion assertion, IEnumerable // Turn the validation of a group of elements using a into // a sequence of calls of each element in the group against a , and // then combines the results of each of these calls. - static ResultReport repeat(IValidatable assertion, IEnumerable input, ValidationContext vc, ValidationState state) + static ResultReport repeat(IValidatable assertion, IEnumerable input, ValidationContext vc, ValidationState state) { return input.ToList() switch { { Count: 0 } => ResultReport.SUCCESS, { Count: 1 } when input.Single() is ValueElementNode ve => assertion.Validate(ve, vc, state), // no index for ValueElementNode { Count: 1 } => assertion.Validate(input.Single(), vc, state.UpdateInstanceLocation(vs => vs.ToIndex(0))), - _ => ResultReport.FromEvidence(input.Select((ma, i) => assertion.Validate(ma, vc, state.UpdateInstanceLocation(vs => vs.ToIndex(i)))).ToList()) + _ => ResultReport.Combine(input.Select((ma, i) => assertion.Validate(ma, vc, state.UpdateInstanceLocation(vs => vs.ToIndex(i)))).ToList()) }; } } @@ -71,7 +81,7 @@ static ResultReport repeat(IValidatable assertion, IEnumerable in /// If the assertion is an , this will simply invoke the /// corresponding method on the validator. If not, it will wrap the single instance as a group /// and call validation for the . - internal static ResultReport ValidateOne(this IAssertion assertion, ITypedElement input, ValidationContext vc, ValidationState state) => + internal static ResultReport ValidateOne(this IAssertion assertion, IScopedNode input, ValidationContext vc, ValidationState state) => assertion switch { IValidatable validatable => validatable.Validate(input, vc, state), diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index 51ebc2a0..7fa674b1 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// This class is also used to track the location of an instance. /// /// An example skeleton path is: "Resource(canonical).childA.childB[sliceB1].childC -> Datatype(canonical).childA" - public class DefinitionPath : PathStack + internal class DefinitionPath : PathStack { private DefinitionPath(PathStackEvent? current) : base(current) { diff --git a/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs index 6574634f..7790c132 100644 --- a/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs @@ -9,13 +9,14 @@ namespace Firely.Fhir.Validation /// /// An interface for objects that let you obtain an by its schema uri. /// - public interface IElementSchemaResolver + internal interface IElementSchemaResolver { /// /// Retrieve a schema by its schema uri. /// /// /// Returns null if the schema was not found. + /// Thrown when the schema was found, but could not be loaded or parsed. ElementSchema? GetSchema(Canonical schemaUri); } } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/IFixedResult.cs b/src/Firely.Fhir.Validation/Schema/IFixedResult.cs index 1f797768..d3d5b707 100644 --- a/src/Firely.Fhir.Validation/Schema/IFixedResult.cs +++ b/src/Firely.Fhir.Validation/Schema/IFixedResult.cs @@ -11,7 +11,7 @@ namespace Firely.Fhir.Validation /// /// These are validators like , and that have /// the result of running them in a validation configured at compilation time. It is used to simplify schema's at compilation time. - public interface IFixedResult + internal interface IFixedResult { /// /// The represented by this instance. diff --git a/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs b/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs index 2d7afacd..a6f48e7c 100644 --- a/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs +++ b/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs @@ -13,11 +13,11 @@ namespace Firely.Fhir.Validation /// The interface for a validation assertion that validates a rule about a set of elements. /// /// A rule that validates cardinality is a great example of this kind of assertion. - public interface IGroupValidatable : IAssertion, IValidatable + internal interface IGroupValidatable : IAssertion, IValidatable { /// /// Validates a set of instances, given a location representative for the group. /// - ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state); + ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state); } } diff --git a/src/Firely.Fhir.Validation/Schema/IValidatable.cs b/src/Firely.Fhir.Validation/Schema/IValidatable.cs index 1c7f2363..0897043c 100644 --- a/src/Firely.Fhir.Validation/Schema/IValidatable.cs +++ b/src/Firely.Fhir.Validation/Schema/IValidatable.cs @@ -9,13 +9,13 @@ namespace Firely.Fhir.Validation { /// - /// Implemented by assertions that work on a single ITypedElement. + /// Implemented by assertions that work on a single . /// - public interface IValidatable : IAssertion + internal interface IValidatable : IAssertion { /// /// Validates a single instance. /// - ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state); + ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state); } } diff --git a/src/Firely.Fhir.Validation/Schema/InstancePath.cs b/src/Firely.Fhir.Validation/Schema/InstancePath.cs index 04c8bec3..c4b7df63 100644 --- a/src/Firely.Fhir.Validation/Schema/InstancePath.cs +++ b/src/Firely.Fhir.Validation/Schema/InstancePath.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// A class representing the location of an instance. /// - public class InstancePath : PathStack + internal class InstancePath : PathStack { private InstancePath(PathStackEvent? current) : base(current) { diff --git a/src/Firely.Fhir.Validation/Schema/PathStack.cs b/src/Firely.Fhir.Validation/Schema/PathStack.cs index 0f351711..cc628d9c 100644 --- a/src/Firely.Fhir.Validation/Schema/PathStack.cs +++ b/src/Firely.Fhir.Validation/Schema/PathStack.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// /// An example skeleton path is: "Resource(canonical).childA.childB[sliceB1].childC -> Datatype(canonical).childA" /// - public abstract class PathStack + internal abstract class PathStack { /// /// Construct a new PathStack and place the current event on top of the stack. @@ -37,7 +37,7 @@ protected PathStack(PathStackEvent? current) /// /// A linked list of events that represent a path. /// - public abstract class PathStackEvent + internal abstract class PathStackEvent { /// /// Add a new event to the path. diff --git a/src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs b/src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs new file mode 100644 index 00000000..08eac1bb --- /dev/null +++ b/src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +namespace Firely.Fhir.Validation +{ + /// + /// Represents an error that occurs when a schema exists, but could not be returned. The reasons + /// are diverse, but examples are unparseable schema's (or underlying StructureDefinitions), specification version errors, + /// duplicate canonical urls, etc. Implementors do not have to raise this exception, but may choose to return null + /// as a result of instead. + /// + public class SchemaResolutionFailedException : System.Exception + { + /// + /// The schema uri that was used to resolve the schema. + /// + public Canonical SchemaUri { get; set; } + + /// + /// Construct an exception given the message and the schema uri. + /// + public SchemaResolutionFailedException(string message, Canonical schemaUri) : base(message) + { + SchemaUri = schemaUri; + } + + /// + /// Construct an exception given the message, the schema uri and the inner exception with details about the reason + /// why the load failed. + /// + public SchemaResolutionFailedException(string message, Canonical schemaUri, System.Exception inner) : base(message, inner) + { + SchemaUri = schemaUri; + } + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs b/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs index 5a8dfb7c..57e35b01 100644 --- a/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs +++ b/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// Represents an informational assertion that has details about the StructureDefinition from which this schema is generated. /// [DataContract] - public record StructureDefinitionInformation : IJsonSerializable + internal record StructureDefinitionInformation : IJsonSerializable { /// /// How a type relates to its baseDefinition. (url: http://hl7.org/fhir/ValueSet/type-derivation-rule) diff --git a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs index 84e41795..69f21522 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs @@ -10,6 +10,8 @@ using Hl7.Fhir.Specification.Terminology; using Hl7.FhirPath; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Firely.Fhir.Validation @@ -22,46 +24,22 @@ namespace Firely.Fhir.Validation public class ValidationContext { /// - /// How to handle the extension Url + /// Initializes a new ValidationContext with the minimal dependencies. /// - public enum ExtensionUrlHandling + internal ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationTerminologyService validateCodeService) { - /// - /// Do not resolve the extension - /// - DontResolve, - /// - /// Add a warning to the validation result when the extension cannot be resolved - /// - WarnIfMissing, - /// - /// Add an error to the validation result when the extension cannot be resolved - /// - ErrorIfMissing, + ElementSchemaResolver = schemaResolver ?? throw new ArgumentNullException(nameof(schemaResolver)); + ValidateCodeService = validateCodeService ?? throw new ArgumentNullException(nameof(validateCodeService)); } /// - /// The validation result when there is an exception in the Terminology Service + /// Initializes a new ValidationContext with no dependencies. At least and + /// must be set before the validator can be used. /// - public enum TerminologyServiceExceptionResult + public ValidationContext() { - /// - /// Return a warning in case of an exception in Terminology Service. - /// - Warning, - /// - /// Return an error in case of an exception in Terminology Service. - /// - Error, - } - - /// - /// Initializes a new ValidationContext with the minimal dependencies. - /// - public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationTerminologyService validateCodeService) - { - ElementSchemaResolver = schemaResolver ?? throw new ArgumentNullException(nameof(schemaResolver)); - ValidateCodeService = validateCodeService ?? throw new ArgumentNullException(nameof(validateCodeService)); + ElementSchemaResolver = new NoopSchemaResolver(); + ValidateCodeService = new NoopTerminologyService(); } /// @@ -71,10 +49,10 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT public ICodeValidationTerminologyService ValidateCodeService; /// - /// A function that maps a type name found in and TypeRefComponent.Code to a resolvable canonical. + /// A function that maps a type name found in TypeRefComponent.Code to a resolvable canonical. /// If not set, it will prefix the type with the standard http://hl7.org/fhir/StructureDefinition prefix. /// - public TypeNameMapper? TypeNameMapper { get; set; } + public TypeNameMapper? TypeNameMapper = null; /// /// The to invoke when the validator calls out to a terminology service and this call @@ -82,20 +60,6 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// public ValidateCodeServiceFailureHandler? HandleValidateCodeServiceFailure = null; - /// The function has 2 input parameters: - /// - valueSetUrl (of type Canonical): the valueSetUrl of the Binding - /// - codes (of type string): a comma separated list of codings - /// - abstract: whether a concept designated as 'abstract' is appropriate/allowed to be use or not - /// - context: the context of the value set - /// - /// Result of the function is . - /// - /// A delegate that determines the result of a failed terminology service call by the validator. - /// - /// The object that was passed to the terminology service. - /// The as returned by the service. - public delegate TerminologyServiceExceptionResult ValidateCodeServiceFailureHandler(ValidateCodeParameters p, FhirOperationException e); - /// /// An that is used when the validator encounters a reference to /// another schema. @@ -103,8 +67,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// Note that this is not just for "external" schemas (e.g. those referred to by Extension.url). The much more common /// case is where the schema for a resource references the schema for the type of one of its elements. /// - public IElementSchemaResolver ElementSchemaResolver; - + internal IElementSchemaResolver ElementSchemaResolver; /// /// A function that resolves an url to an external instance, parsed as an . @@ -115,13 +78,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// to contained resources will always be followed. If this property is not set, references will be /// ignored. /// - public ExternalReferenceResolver? ResolveExternalReference = null; - - - /// - /// A delegate that resolves a reference to another resource, outside of the current instance under validation. - /// - public delegate ITypedElement? ExternalReferenceResolver(string reference, string location); + internal ExternalReferenceResolver? ResolveExternalReference = null; /// /// An instance of the FhirPath compiler to use when evaluating constraints @@ -136,21 +93,12 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// https://www.hl7.org/fhir/best-practices.html for more information. public ValidateBestPracticesSeverity ConstraintBestPractices = ValidateBestPracticesSeverity.Warning; - /// /// The to invoke when a is encountered. If not set, the list of profiles /// is used as encountered in the instance. /// public MetaProfileSelector? SelectMetaProfiles = null; - /// - /// A function that determines which profiles in the validator should use to validate this instance. - /// - /// The location within the resource where the Meta.profile is found. - /// The original list of profiles found in Meta.profile. - /// A new set of meta profiles that the validator will use for validation of this instance. - public delegate Canonical[] MetaProfileSelector(string location, Canonical[] originalProfiles); - /// /// The to invoke when an is encountered in an instance. /// If not set, then a validation of an Extension will warn if the extension cannot be resolved, or will return an error when @@ -158,49 +106,42 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// public ExtensionUrlFollower? FollowExtensionUrl = null; - /// - /// A function to determine how to handle an extension that is encountered in the instance. - /// - /// The location within the resource where the Meta.profile is found. - /// The canonical of the extension that was encountered in the instance. - public delegate ExtensionUrlHandling ExtensionUrlFollower(string location, Canonical? url); - /// /// A function to include the assertion in the validation or not. If the function is left empty (null) then all the /// assertions are processed in the validation. /// - public Predicate? IncludeFilter = null; + public ICollection> IncludeFilters = new List>(); /// /// A function to exclude the assertion in the validation or not. If the function is left empty (null) then all the /// assertions are processed in the validation. /// - public Predicate? ExcludeFilter = DEFAULT_EXCLUDE_FILTER; + public ICollection> ExcludeFilters = new List> { DEFAULTEXCLUDEFILTER }; /// - /// The default for , which will exclude FhirPath invariant dom-6 from triggering. + /// The default for , which will exclude FhirPath invariant dom-6 from triggering. /// - public static readonly Predicate DEFAULT_EXCLUDE_FILTER = a => a is FhirPathValidator fhirPathAssertion && fhirPathAssertion.Key == "dom-6"; + public static readonly Predicate DEFAULTEXCLUDEFILTER = a => a is FhirPathValidator fhirPathAssertion && fhirPathAssertion.Key == "dom-6"; /// /// Determines whether a given assertion is included in the validation. The outcome is determined by - /// and . + /// and . /// - public bool Filter(IAssertion a) => - (IncludeFilter is null || IncludeFilter(a)) && - (ExcludeFilter is null || !ExcludeFilter(a)); + internal bool Filter(IAssertion a) => + (IncludeFilters.Count == 0 || IncludeFilters.Any(inc => inc(a))) && + !ExcludeFilters.Any(exc => exc(a)); /// /// Whether to add trace messages to the validation result. /// - public bool TraceEnabled = false; + internal bool TraceEnabled = false; /// /// Invokes a factory method for assertions only when tracing is on. /// /// /// - public ResultReport TraceResult(Func p) => + internal ResultReport TraceResult(Func p) => TraceEnabled ? p().AsResult() : ResultReport.SUCCESS; /// @@ -208,7 +149,7 @@ public ResultReport TraceResult(Func p) => /// reference other schemas. When any of these required dependencies are accessed, a will /// be thrown. /// - public static ValidationContext BuildMinimalContext(ICodeValidationTerminologyService? validateCodeService = null, + internal static ValidationContext BuildMinimalContext(ICodeValidationTerminologyService? validateCodeService = null, IElementSchemaResolver? schemaResolver = null, FhirPathCompiler? fpCompiler = null) => new(schemaResolver ?? new NoopSchemaResolver(), validateCodeService ?? new NoopTerminologyService()) { @@ -234,4 +175,72 @@ internal class NoopTerminologyService : ICodeValidationTerminologyService public Task ValueSetValidateCode(Parameters parameters, string? id = null, bool useGet = false) => throw new NotImplementedException(); } } + + /// + /// How to handle the extension Url + /// + public enum ExtensionUrlHandling + { + /// + /// Do not resolve the extension + /// + DontResolve, + /// + /// Add a warning to the validation result when the extension cannot be resolved + /// + WarnIfMissing, + /// + /// Add an error to the validation result when the extension cannot be resolved + /// + ErrorIfMissing, + } + + /// + /// The validation result when there is an exception in the Terminology Service + /// + public enum TerminologyServiceExceptionResult + { + /// + /// Return a warning in case of an exception in Terminology Service. + /// + Warning, + /// + /// Return an error in case of an exception in Terminology Service. + /// + Error, + } + + /// The function has 2 input parameters: + /// - valueSetUrl (of type Canonical): the valueSetUrl of the Binding + /// - codes (of type string): a comma separated list of codings + /// - abstract: whether a concept designated as 'abstract' is appropriate/allowed to be use or not + /// - context: the context of the value set + /// + /// Result of the function is . + /// + /// A delegate that determines the result of a failed terminology service call by the validator. + /// + /// The object that was passed to the terminology service. + /// The as returned by the service. + public delegate TerminologyServiceExceptionResult ValidateCodeServiceFailureHandler(ValidateCodeParameters p, FhirOperationException e); + + /// + /// A delegate that resolves a reference to another resource, outside of the current instance under validation. + /// + internal delegate ITypedElement? ExternalReferenceResolver(string reference, string location); + + /// + /// A function that determines which profiles in the validator should use to validate this instance. + /// + /// The location within the resource where the Meta.profile is found. + /// The original list of profiles found in Meta.profile. + /// A new set of meta profiles that the validator will use for validation of this instance. + public delegate Canonical[] MetaProfileSelector(string location, Canonical[] originalProfiles); + + /// + /// A function to determine how to handle an extension that is encountered in the instance. + /// + /// The location within the resource where the Meta.profile is found. + /// The canonical of the extension that was encountered in the instance. + public delegate ExtensionUrlHandling ExtensionUrlFollower(string location, Canonical? url); } diff --git a/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs b/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs index 3a7d19dc..bb5bd885 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation /// validated, and what the previous result was. Since it keeps track of running validations, it /// can also be used to detect loops. /// - public class ValidationLogger + internal class ValidationLogger { //TODO: I think this should have a parent logger /// diff --git a/src/Firely.Fhir.Validation/Schema/ValidationState.cs b/src/Firely.Fhir.Validation/Schema/ValidationState.cs index ba5a5452..ce14631f 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationState.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationState.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// Represents thread-safe, shareable state for a single run of the validator. /// - public record ValidationState + internal record ValidationState { /// /// A container for state properties that are shared across a full run of the validator. diff --git a/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs b/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs index 19307f63..1e056d67 100644 --- a/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs +++ b/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs @@ -6,24 +6,28 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Language; -using Hl7.Fhir.Serialization; using System.Collections.Generic; using System.Linq; namespace Firely.Fhir.Validation { - internal class ValueElementNode : BaseTypedElement + internal class ValueElementNode : IScopedNode { - public ValueElementNode(ITypedElement wrapped) : base(wrapped) + private readonly IScopedNode _wrapped; + + public ValueElementNode(IScopedNode wrapped) { + _wrapped = wrapped; } - public override string Name => "value"; + public IScopedNode? Parent => _wrapped.Parent; + + public string Name => "value"; - public override string InstanceType => TypeSpecifier.ForNativeType(Wrapped.Value.GetType()).FullName; + public string InstanceType => TypeSpecifier.ForNativeType(_wrapped.Value.GetType()).FullName; - public override string Location => $"{Wrapped.Location}.value"; + public object Value => _wrapped.Value; - public override IEnumerable Children(string? name = null) => Enumerable.Empty(); + public IEnumerable Children(string? name = null) => Enumerable.Empty(); } } diff --git a/src/Firely.Fhir.Validation/Support/AggregationMode.cs b/src/Firely.Fhir.Validation/Support/AggregationMode.cs index bc471ecb..38e65cda 100644 --- a/src/Firely.Fhir.Validation/Support/AggregationMode.cs +++ b/src/Firely.Fhir.Validation/Support/AggregationMode.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// (system: http://hl7.org/fhir/resource-aggregation-mode) /// [FhirEnumeration("AggregationMode")] - public enum AggregationMode + internal enum AggregationMode { /// /// The reference is a local reference to a contained resource. diff --git a/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs index 8afd3206..8257b82b 100644 --- a/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// , unless this has been resolved before, in which /// case resolution is done immediately from a cache. /// - public class CachedElementSchemaResolver : IElementSchemaResolver + internal class CachedElementSchemaResolver : IElementSchemaResolver { private readonly ConcurrentDictionary _cache = new(); diff --git a/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs b/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs index a11eec1a..aefe7fda 100644 --- a/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs +++ b/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// /// This class is highly dependent on the presence of the FHIR-specific /// in ElementSchema, so it will only work for types that are derived from FHIR StructureDefinitions. - public static class FhirSchemaGroupAnalyzer + internal static class FhirSchemaGroupAnalyzer { /// /// The result of resolving a schema: either a schema, or a detailing the failure. @@ -105,7 +105,7 @@ public static ResultReport ValidateConsistency(FhirSchema? actualType, Canonical } - return ResultReport.FromEvidence(issues); + return ResultReport.Combine(issues); } /// diff --git a/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs b/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs index b176b354..4f8d5107 100644 --- a/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// /// - public static class IAssertionExtensions + internal static class IAssertionExtensions { /// /// diff --git a/src/Firely.Fhir.Validation/Support/IExternalReferenceResolver.cs b/src/Firely.Fhir.Validation/Support/IExternalReferenceResolver.cs new file mode 100644 index 00000000..e60ac2b2 --- /dev/null +++ b/src/Firely.Fhir.Validation/Support/IExternalReferenceResolver.cs @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using System.Threading.Tasks; + +namespace Firely.Fhir.Validation +{ + /// + /// A service that can resolve external references to other resources. + /// + public interface IExternalReferenceResolver + { + /// + /// Resolves the reference to a resource. The returned object must be either a or + /// + /// The resource or element node, or null if the reference could not be resolved. + Task ResolveAsync(string reference); + } + +} diff --git a/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs b/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs new file mode 100644 index 00000000..de8bfb8b --- /dev/null +++ b/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Firely.Fhir.Validation +{ + /// + /// An implementation of that uses an in-memory dictionary. + /// + public class InMemoryExternalReferenceResolver : Dictionary, IExternalReferenceResolver + { + /// + public Task ResolveAsync(string reference) => + Task.FromResult(TryGetValue(reference, out var resource) ? resource : null); + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs b/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs index 1b979a3a..319aade6 100644 --- a/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs +++ b/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs @@ -8,12 +8,12 @@ using System; -namespace Hl7.Fhir.Validation +namespace Firely.Fhir.Validation { /// /// Exception about an error encountered in an ElementDefinitions. /// - public class IncorrectElementDefinitionException : Exception + internal class IncorrectElementDefinitionException : Exception { /// /// Exception about an error encountered in an ElementDefinitions. diff --git a/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs index 6f724dfc..90815f09 100644 --- a/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation /// This implementation of will resolve a single uri against multiple /// child resolvers: the result of the first child resolver to return a non-null resolution will be used. /// - public class MultiElementSchemaResolver : IElementSchemaResolver + internal class MultiElementSchemaResolver : IElementSchemaResolver { /// /// The set of child used as the source for resolving schemas, diff --git a/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs b/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs index 664c9925..0dc4f42d 100644 --- a/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs +++ b/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// (system: http://hl7.org/fhir/reference-version-rules) /// [FhirEnumeration("ReferenceVersionRules")] - public enum ReferenceVersionRules + internal enum ReferenceVersionRules { /// /// The reference may be either version independent or version specific diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs new file mode 100644 index 00000000..c4a578bb --- /dev/null +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -0,0 +1,91 @@ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Introspection; +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation +{ + /// + /// Helper methods to convert between and + /// + internal static class ScopedNodeExtensions + { + /// + /// + /// + /// + /// + /// + public static IScopedNode ToScopedNode(this IReadOnlyDictionary node, ModelInspector? inspector = null) + { + inspector ??= ModelInspector.ForAssembly(node.GetType().Assembly); + return new ScopedNodeOnDictionary(inspector, node.GetType().Name, node); + } + } + + internal class ScopedNodeOnDictionary : IScopedNode + { + private readonly IReadOnlyDictionary _wrapped; + private readonly IScopedNode? _parentNode; + private readonly ModelInspector _inspector; + private readonly ClassMapping? _myClassMapping; + private readonly string _name; + + public ScopedNodeOnDictionary(ModelInspector inspector, string rootName, IReadOnlyDictionary wrapped, IScopedNode? parentNode = null) + { + (_wrapped, _parentNode, _inspector, _name) = (wrapped, parentNode, inspector, rootName); + _myClassMapping = _inspector.FindOrImportClassMapping(_wrapped.GetType()); + InstanceType = (_myClassMapping as IStructureDefinitionSummary)?.TypeName; + } + + public IScopedNode? Parent => _parentNode; + + public string Name => _name; + +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + public string? InstanceType { get; private set; } + + public object? Value => _wrapped.TryGetValue("value", out var value) && isNETPrimitiveType(value) ? value : null; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + + public IEnumerable Children(string? name = null) + { + IEnumerable> children = name switch + { + not null => + _wrapped.TryGetValue(name, out var value) && value is not null ? + new KeyValuePair[] { KeyValuePair.Create(name, value) } + : Enumerable.Empty>(), + _ => _wrapped + }; + + foreach (var child in children) + { + if (child.Value is IList coll) + { + foreach (var childValue in coll) + if (childValue is IReadOnlyDictionary dict) + yield return new ScopedNodeOnDictionary(_inspector, child.Key, dict, this); + } + else if (child.Value is IReadOnlyDictionary re) + yield return new ScopedNodeOnDictionary(_inspector, child.Key, re, this); + else if (child.Key != "value" && isNETPrimitiveType(child.Value)) + yield return new ConstantElement(child.Key, child.Value.GetType().Name, child.Value, this); + + } + } + + private bool isNETPrimitiveType(object a) + => a is string or bool or decimal or DateTimeOffset or int or long or byte[] or XHtml; + + internal record ConstantElement(string Name, string InstanceType, object Value, IScopedNode Parent) : IScopedNode + { + public IEnumerable Children(string? name) => + Enumerable.Empty(); + } + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs new file mode 100644 index 00000000..6c4eff13 --- /dev/null +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs @@ -0,0 +1,20 @@ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Introspection; +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using Newtonsoft.Json.Linq; + +namespace Firely.Fhir.Validation +{ + internal static class ScopedNodeJsonExtensions + { + public static JToken ToJToken(this DataType instance) + { + if (instance is PrimitiveType pt) + return pt.ObjectValue is not null ? new JValue(pt.ObjectValue) : JValue.CreateNull(); + + var modelInspector = ModelInspector.ForAssembly(instance.GetType().Assembly); + return instance.ToTypedElement(modelInspector).ToJObject(); + } + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs index 5878484f..5474d91c 100644 --- a/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs @@ -18,7 +18,7 @@ namespace Firely.Fhir.Validation /// for the full list). These types are the "atomic" types StructureDefinitions in FHIR are based /// on, so themselves have no source definition to convert from. /// - public class SystemNamespaceElementSchemaResolver : IElementSchemaResolver + internal class SystemNamespaceElementSchemaResolver : IElementSchemaResolver { // Note: we could move these to the SDK, but once our revision of the // type system is done, we can retrieve this using reflection too... diff --git a/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs b/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs index 24945141..262e22d7 100644 --- a/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs +++ b/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs @@ -4,12 +4,10 @@ * via any medium is strictly prohibited. */ -using Hl7.Fhir.ElementModel; - namespace Firely.Fhir.Validation { /// - /// Function that maps a local type name (as found on e.g. or ElementDefinition.TypeRef.Code) to + /// Function that maps a local type name (as found on e.g. ElementDefinition.TypeRef.Code to /// to a resolvable url. /// /// May return null if the local name has no mapping to a resolvable canonical url. @@ -18,7 +16,7 @@ namespace Firely.Fhir.Validation /// /// Extension methods for use on top of . /// - public static class TypeNameMapperExtensions + internal static class TypeNameMapperExtensions { /// /// Will invoke the , if set. If there is no mapper, or the mapper returns diff --git a/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj b/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj deleted file mode 100644 index 61d791cc..00000000 --- a/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - Firely Validation shims for R4 - R4 specific shims to simulate the "old" validator on top of the new ElementSchema-based validator. - HL7;FHIR;Validation;Utility; - Firely.Fhir.Validation.Compilation - - - - - - - - - - - \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.R4/fhir-single.zip b/src/Hl7.Fhir.Validation.R4/fhir-single.zip deleted file mode 100644 index 80eb23a8..00000000 Binary files a/src/Hl7.Fhir.Validation.R4/fhir-single.zip and /dev/null differ diff --git a/src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs b/src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs deleted file mode 100644 index 94d87346..00000000 --- a/src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 - */ - -namespace Hl7.Fhir.Validation -{ - /// - /// Represents whether to treat violations of the invariants marked as "best practice" as errors or as warnings. - /// - public enum ConstraintBestPracticesSeverity - { - /// - /// A failed "Best Practice" invariant should result in a warning. - /// - Warning, - - /// - /// A failed "Best Practice" invariant should result in an error. - /// - Error - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs b/src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs deleted file mode 100644 index 4d4c0816..00000000 --- a/src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 - -// Needed for pre-net5 targets to support "init"-type setters (C# 9.0). -// See also https://github.com/dotnet/roslyn/issues/45510 - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices -{ - /// - /// Reserved to be used by the compiler for tracking metadata. - /// This class should not be used by developers in source code. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal static class IsExternalInit - { - } -} - -#endif \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs b/src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs deleted file mode 100644 index d8a86af7..00000000 --- a/src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.ElementModel; -using Hl7.Fhir.Model; -using System.Collections.Generic; - -namespace Hl7.Fhir.Validation -{ - /// - /// Add support for validating against Base subclasses (instead of ITypedElement) to the Validator - /// - public static class PocoValidationExtensions - { - /// - /// Validate an instance, use the instance's type to pick the relevant profile to validate against. - /// - public static OperationOutcome Validate(this Validator me, Base instance) - { - return me.Validate(instance.ToTypedElement()); - } - - /// - /// Validate an instance against a given set of profiles. - /// - public static OperationOutcome Validate(this Validator me, Base instance, params string[] definitionUri) - { - return me.Validate(instance.ToTypedElement(), definitionUri); - } - - /// - /// Validate an instance against a given set of profiles. - /// - public static OperationOutcome Validate(this Validator me, Base instance, StructureDefinition structureDefinition) - { - return me.Validate(instance.ToTypedElement(), structureDefinition); - } - - /// - /// Validate an instance against a given set of profiles. - /// - public static OperationOutcome Validate(this Validator me, Base instance, IEnumerable structureDefinitions) - { - return me.Validate(instance.ToTypedElement(), structureDefinitions); - } - } -} diff --git a/src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs b/src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs deleted file mode 100644 index a52fb39d..00000000 --- a/src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE - */ - -using Hl7.Fhir.Utility; -using System; -using System.Xml.Schema; - -#nullable enable - -namespace Hl7.Fhir.Specification.Source -{ - /// - /// A class that retrieves, compiles and caches an necessary to validate FHIR XML. - /// - public class SchemaCollection - { - private const string FHIRSINGLE_XSD_RESOURCENAME = "fhir-single.zip"; - - /// - /// Constructs a SchemaCollection which retrieves XML schemas from the given . - /// - [Obsolete("The FHIR-supplied XSD schemas in SchemaCollection are now shipped as embedded resources, so you can no longer use a specific IArtifactSource to read from.")] - public SchemaCollection(IArtifactSource _) : this() - { - // Nothing - } - - /// - /// Constructs a SchemaCollection which retrieves XML schemas from the default source. - /// - /// The default source is the source returned by . - public SchemaCollection() - { - _validationSchemaSet = new(SerializationUtil.BASEFHIRSCHEMAS, typeof(SchemaCollection).Assembly, FHIRSINGLE_XSD_RESOURCENAME); - } - - private readonly IncludedXsdSchemaSet _validationSchemaSet; - - /// - /// Return an XmlSchemaSet that contains the minimal set necessary to validate FHIR XML. - /// - public XmlSchemaSet MinimalSchemas => _validationSchemaSet.CompiledSchemas; - - /// - /// Returns the schemas necessary to use XML validation on FHIR resources. - /// - public static XmlSchemaSet ValidationSchemaSet => Default.MinimalSchemas; - - /// - /// A singleton instance of the - /// -#pragma warning disable IDE1006 // Naming Styles - public static readonly SchemaCollection Default = new(); -#pragma warning restore IDE1006 // Naming Styles - } -} - -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs b/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs deleted file mode 100644 index f3daae48..00000000 --- a/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.ElementModel; -using Hl7.Fhir.Specification; -using Hl7.Fhir.Specification.Snapshot; -using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Specification.Terminology; -using Hl7.Fhir.Utility; -using System; -using System.Xml.Schema; - -namespace Hl7.Fhir.Validation -{ - /// - /// Configuration settings for the class. - /// - public class ValidationSettings - { - /// - /// The used to map instance types encountered in - /// to canonicals when a profile for these types needs to be retrieved. - /// - public StructureDefinitionSummaryProvider.TypeNameMapper? ResourceMapping { get; set; } - - /// - /// The resolver to use when canonicals or references to other resources are encountered in the instance. - /// - /// Most of the time this resolver is used when resolving canonicals to profiles or valuesets, - /// but it may also be used to resolve references encountered in instance data. - /// See for more information. - public IResourceResolver? ResourceResolver { get; set; } - - /// - /// The terminology service to use to validate coded instance data. - /// - public ITerminologyService? TerminologyService { get; set; } - - /// - /// An instance of the FhirPath compiler to use when evaluating constraints - /// (provide this if you have custom functions included in the symbol table) - /// - /// If this property is not set, the validator will - /// use a FhirPathCompiler with all FHIR extensions installed. - public Hl7.FhirPath.FhirPathCompiler? FhirPathCompiler { get; set; } - - /// - /// The validator needs StructureDefinitions to have a snapshot form to function. If a StructureDefinition - /// without a snapshot is encountered, should the validator generate the snapshot from the differential - /// present in the StructureDefinition? Default is 'false'. - /// - public bool GenerateSnapshot { get; set; } // = false - - /// - /// Configuration settings for the snapshot generator - /// (if the property is enabled). - /// Never returns null. Assigning null reverts back to default settings. - /// - public SnapshotGeneratorSettings? GenerateSnapshotSettings - { - get => _generateSnapshotSettings; - set => _generateSnapshotSettings = value?.Clone() ?? SnapshotGeneratorSettings.CreateDefault(); - } - - private SnapshotGeneratorSettings _generateSnapshotSettings = SnapshotGeneratorSettings.CreateDefault(); - - /// - /// Include informational tracing information in the validation output. Useful for debugging purposes. Default is 'false'. - /// - public bool Trace { get; set; } // = false; - - /// - /// StructureDefinition may contain FhirPath constraints to enfore invariants in the data that cannot - /// be expresses using StructureDefinition alone. This validation can be turned off for performance or - /// debugging purposes. Default is 'false'. - /// - public bool SkipConstraintValidation { get; set; } // = false; - - /// - /// A list of constraints to be ignored by the validator. Default values are dom-6, rng-2, "bdl-8" and "cnl-0" - /// - public string[]? ConstraintsToIgnore { get; set; } = new string[] { "dom-6", "rng-2", "bdl-8", "cnl-0" }; - - /// - /// If a reference is encountered that references to a resource outside of the current instance being validated, - /// this setting controls whether the validator will call out to the ResourceResolver to try to resolve the - /// external reference. - /// - /// References that refer to resources inside the current instance (i.e. - /// contained resources, Bundle entries) will always be followed and validated. See - /// for more information. - public bool ResolveExternalReferences { get; set; } // = false; - - /// - /// If set to true (and the XDocument specific overloads of validate() are used), the validator will run - /// .NET XSD validation prior to running profile validation - /// - public bool EnableXsdValidation { get; set; } // = false; - - /// - /// Choose whether the validator will treat the violations of the invariants marked as best practices as errors or as warnings. - /// - public ConstraintBestPracticesSeverity ConstraintBestPracticesSeverity { get; set; } - - /// - /// Determine where to retrieve the XSD schemas from when when Xsd validation is enabled and run. - /// - /// If this is not set, the default FHIR XSDs from the specification will be used. - public XmlSchemaSet? XsdSchemaCollection { get; set; } - - /// - /// Default constructor. Creates a new instance with default property values. - /// - public ValidationSettings() { } - - /// Clone constructor. Generates a new instance initialized from the state of the specified instance. - /// The specified argument is null. - public ValidationSettings(ValidationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - other.CopyTo(this); - } - - /// Copy all configuration settings to another instance. - /// Another instance. - /// The specified argument is null. - public void CopyTo(ValidationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - - other.ConstraintBestPracticesSeverity = ConstraintBestPracticesSeverity; - other.GenerateSnapshot = GenerateSnapshot; - other.GenerateSnapshotSettings = GenerateSnapshotSettings?.Clone(); - other.EnableXsdValidation = EnableXsdValidation; - other.ResolveExternalReferences = ResolveExternalReferences; - other.ResourceResolver = ResourceResolver; - other.SkipConstraintValidation = SkipConstraintValidation; - other.TerminologyService = TerminologyService; - other.Trace = Trace; - other.FhirPathCompiler = FhirPathCompiler; - other.ResourceMapping = ResourceMapping; - other.XsdSchemaCollection = XsdSchemaCollection; - other.ConstraintsToIgnore = ConstraintsToIgnore; - } - - /// Creates a new object that is a copy of the current instance. - public ValidationSettings Clone() => new(this); - - /// Creates a new instance with default property values. - public static ValidationSettings CreateDefault() => new(); - - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs deleted file mode 100644 index 98acce05..00000000 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ /dev/null @@ -1,445 +0,0 @@ -/* - * 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.ElementModel; -using Hl7.Fhir.FhirPath; -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Specification.Navigation; -using Hl7.Fhir.Specification.Snapshot; -using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Support; -using Hl7.FhirPath; -using Hl7.FhirPath.Expressions; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Hl7.Fhir.Validation -{ - /// - /// This is a shim, simulating the interface of the original SDK validator (pre 5.0 SDK) on top of the - /// new Firely Validator SDK. - /// - public class Validator - { - /// - /// The current settings used by the validator when calling one of the Validate methods. - /// - public ValidationSettings Settings { get; private set; } - - /// - /// When the validator encounters a StructureDefinition without a snapshot, and is true, - /// this event will be invoked to obtain a snapshot for that StructureDefinition. - /// - /// If this event is not set, a snapshot will be created using a with - /// the settings in or the default settings if those - /// are not set. This snapshot generator will use the to fetch - /// additional StructureDefinitions encountered while snapshotting. - public event EventHandler? OnSnapshotNeeded; - - /// - /// When is true, this event will be called to - /// resolve references encountered in the instance data (including those encountered in calls to the - /// FhirPath resolve() function). - /// - /// If this event is not set, or resolution resulted in an Exception, resolution is tried using - /// the , by calling . - public event EventHandler? OnExternalResolutionNeeded; - - private SnapshotGenerator? _snapshotGenerator; - - internal SnapshotGenerator? SnapshotGenerator - { - get - { - if (_snapshotGenerator == null) - { - var resolver = Settings.ResourceResolver; - if (resolver != null) - { - SnapshotGeneratorSettings settings = Settings.GenerateSnapshotSettings ?? SnapshotGeneratorSettings.CreateDefault(); - _snapshotGenerator = new SnapshotGenerator(resolver, settings); - } - - } - return _snapshotGenerator; - } - } - - private FhirPathCompiler? _fpCompiler; - - /// - /// The FhirPath compiler to use for handling the FhirPath-based invariants in the - /// StructureDefinition. - /// - /// This property will return if set, - /// otherwise it will construct a singleton FhirPathCompiler with all FHIR extensions installed. - internal FhirPathCompiler FpCompiler - { - get - { - // Use a provided compiler - if (Settings.FhirPathCompiler != null) - return Settings.FhirPathCompiler; - - if (_fpCompiler == null) - { - var symbolTable = new SymbolTable(); - symbolTable.AddStandardFP(); - symbolTable.AddFhirExtensions(); - - _fpCompiler = new FhirPathCompiler(symbolTable); - - Settings.FhirPathCompiler = _fpCompiler; - } - - return _fpCompiler; - } - } - - /// - /// Construct a new validator with the given settings. - /// - /// - public Validator(ValidationSettings settings) - { - Settings = settings.Clone(); - } - - /// - /// Construct a new validator with default settings. - /// - public Validator() : this(ValidationSettings.CreateDefault()) - { - } - - /// - /// Validate an instance, use the instance's to pick the relevant profile to validate against. - /// - public OperationOutcome Validate(ITypedElement instance) - { - throw new NotImplementedException(); - //var state = new ValidationState(); - //var result = ValidateInternal(instance, declaredTypeProfile: null, statedCanonicals: null, statedProfiles: null, state: state) - // .RemoveDuplicateMessages(); - //result.SetAnnotation(state); - //return result; - } - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, params string[] definitionUris) => - Validate(instance, (IEnumerable)definitionUris); - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, IEnumerable definitionUris) - { - throw new NotImplementedException(); - //var state = new ValidationState(); - //var result = ValidateInternal(instance, declaredTypeProfile: null, statedCanonicals: definitionUris, statedProfiles: null, state: state) - // .RemoveDuplicateMessages(); - //result.SetAnnotation(state); - //return result; - } - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, params StructureDefinition[] structureDefinitions) => - Validate(instance, (IEnumerable)structureDefinitions); - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, IEnumerable structureDefinitions) - { - throw new NotImplementedException(); - //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 ValidateInternal( - ITypedElement instance, - string declaredTypeProfile, - IEnumerable statedCanonicals, - IEnumerable statedProfiles, - object state) // used to be ValidationState - { - throw new NotImplementedException(); - //var resolutionContext = instance switch - //{ - // { InstanceType: "Extension" } when instance.Name == "modifierExtension" => ProfileAssertion.ResolutionContext.InModifierExtension, - // { InstanceType: "Extension" } => ProfileAssertion.ResolutionContext.InExtension, - // _ => ProfileAssertion.ResolutionContext.Elsewhere - //}; - - //var processor = new ProfilePreprocessor(profileResolutionNeeded, snapshotGenerationNeeded, instance, declaredTypeProfile, statedProfiles, statedCanonicals, Settings.ResourceMapping, resolutionContext); - //var outcome = processor.Process(); - - //// Note: only start validating if the profiles are complete and consistent - //if (outcome.Success) - // outcome.Add(ValidateInternal(instance, processor.Result, state)); - - //return outcome; - } - - 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, object 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 ValidateInternal(ITypedElement elementNav, IEnumerable definitions, object state) - { - throw new NotImplementedException(); - //var outcome = new OperationOutcome(); - //var instance = elementNav.ToScopedNode(); - - //try - //{ - // var allDefinitions = definitions.ToList(); - - // if (allDefinitions.Count() == 1) - // outcome.Add(startValidation(allDefinitions.Single(), instance, state)); - // else - // { - // var validators = allDefinitions.Select(nav => createValidator(nav)); - // outcome.Add(this.Combine("the list of profiles", BatchValidationMode.All, instance, validators)); - // } - //} - //catch (Exception e) - //{ - // outcome.AddIssue($"Internal logic failure: {e.Message}", Issue.PROCESSING_CATASTROPHIC_FAILURE, instance); - //} - - //return outcome; - - //Func createValidator(ElementDefinitionNavigator nav) => - // () => startValidation(nav, instance, state); - - } - - private OperationOutcome startValidation(ElementDefinitionNavigator definition, ScopedNode instance, object state) - { - throw new NotImplementedException(); - //// 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, object state) - { - var outcome = new OperationOutcome(); - - try - { - throw new NotImplementedException(); - } - catch (StructuralTypeException te) - { - outcome.AddIssue(te.Message, Issue.CONTENT_ELEMENT_HAS_INCORRECT_TYPE, instance.Location); - return outcome; - } - } - - - internal OperationOutcome.IssueComponent? Trace(OperationOutcome outcome, string message, Issue issue, string location) => - Settings.Trace || issue.Severity != OperationOutcome.IssueSeverity.Information - ? outcome.AddIssue(message, issue, location) - : null; - - internal OperationOutcome.IssueComponent? Trace(OperationOutcome outcome, string message, Issue issue, ITypedElement location) => - Settings.Trace || issue.Severity != OperationOutcome.IssueSeverity.Information - ? Trace(outcome, message, issue, location.Location) - : null; - - internal ITypedElement? ExternalReferenceResolutionNeeded(string reference, OperationOutcome outcome, string path) - { - if (!Settings.ResolveExternalReferences) return null; - - try - { - // Default implementation: call event - if (OnExternalResolutionNeeded != null) - { - var args = new OnResolveResourceReferenceEventArgs(reference); - OnExternalResolutionNeeded(this, args); - return args.Result; - } - } - catch (Exception e) - { - Trace(outcome, "External resolution of '{reference}' caused an error: " + e.Message, Issue.UNAVAILABLE_REFERENCED_RESOURCE, path); - } - - // Else, try to resolve using the given ResourceResolver - // (note: this also happens when the external resolution above threw an exception) - if (Settings.ResourceResolver != null) - { - try - { - var poco = Settings.ResourceResolver.ResolveByUri(reference); - if (poco != null) - return poco.ToTypedElement(); - } - catch (Exception e) - { - Trace(outcome, $"Resolution of reference '{reference}' using the Resolver SDK failed: " + e.Message, Issue.UNAVAILABLE_REFERENCED_RESOURCE, path); - } - } - - return null; // Sorry, nothing worked - } - - - // Note: this modifies an SD that is passed to us and will alter a possibly cached - // object shared amongst other threads. This is generally useful and saves considerable - // time when the same snapshot is needed again, but may result in side-effects - private OperationOutcome snapshotGenerationNeeded(StructureDefinition definition) - { - if (!Settings.GenerateSnapshot) return new OperationOutcome(); - - // Default implementation: call event - if (OnSnapshotNeeded is not null && Settings.ResourceResolver is not null) - { - var eventData = new OnSnapshotNeededEventArgs(definition, Settings.ResourceResolver); - OnSnapshotNeeded(this, eventData); - return eventData.Result ?? new OperationOutcome(); - } - - // Else, expand, depending on our configuration - var generator = SnapshotGenerator; - if (generator != null) - { - //TODO: make everything async in 2.x validator -#pragma warning disable CS0618 // Type or member is obsolete - generator.Update(definition); -#pragma warning restore CS0618 // Type or member is obsolete - -#if DEBUG - // TODO: Validation Async Support - string xml = (new FhirXmlSerializer()).SerializeToString(definition); - string name = definition.Id ?? definition.Name.Replace(" ", "").Replace("/", ""); - var dir = Path.Combine(Path.GetTempPath(), "validation"); - - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - File.WriteAllText(Path.Combine(dir, name) + ".StructureDefinition.xml", xml); -#endif - - - return generator.Outcome ?? new OperationOutcome(); - } - - return new OperationOutcome(); - } - } - - - /// - /// Arguments supplied to the event when invoked. - /// - public class OnSnapshotNeededEventArgs : EventArgs - { - /// - /// Construct new events args. - /// - public OnSnapshotNeededEventArgs(StructureDefinition definition, IResourceResolver resolver) - { - Definition = definition; - Resolver = resolver; - } - - /// - /// The which needs to be snapshotted. The event should - /// generate the snapshot in the property of this - /// instance. - /// - public StructureDefinition Definition { get; } - - /// - /// The resolver to use when generating the snapshot. - /// - public IResourceResolver Resolver { get; } - - /// - /// An that represents success or issues encountered while - /// creating the snapshot. - /// - public OperationOutcome? Result { get; set; } - } - - /// - /// Arguments supplied to the event when invoked. - /// - public class OnResolveResourceReferenceEventArgs : EventArgs - { - /// - /// Construct new events args. - /// - public OnResolveResourceReferenceEventArgs(string reference) - { - Reference = reference; - } - - /// - /// The reference that should be resolved to an instance. - /// - public string Reference { get; } - - /// - /// The resolved instance. - /// - public ITypedElement? Result { get; set; } - } - - - // Marked obsolete at 2022-11-22, EK - [Obsolete("This enumeration is not used (publicly) by the SDK and will be removed from the public surface in the future.")] -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public enum BatchValidationMode - { - All, - Any, - Once - } -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member -} diff --git a/src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs b/src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs deleted file mode 100644 index 63032872..00000000 --- a/src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 - -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Support; -using System; -using System.Collections.Generic; -using System.Xml; -using System.Xml.Linq; -using System.Xml.Schema; - -namespace Hl7.Fhir.Validation -{ - /// - /// Add support for validating data straight from an . - /// - public static class XmlValidationExtensions - { - private static XmlSchemaSet getSchemaSetFromSettings(Validator v) => v.Settings.XsdSchemaCollection ?? - SchemaCollection.ValidationSchemaSet; - - /// - /// Validate an instance serialized in xml, use the instance's type to pick the relevant profile and - /// FHIR XSD schema to validate against. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco)); - - return result; - } - - /// - /// Validate an instance serialized in xml against a given set of profiles and the - /// relevant XSD profile from the FHIR specification. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance, params string[] definitionUris) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco, definitionUris)); - - return result; - } - - /// - /// Validate an instance serialized in xml against a given set of profiles and the - /// relevant XSD profile from the FHIR specification. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance, StructureDefinition structureDefinition) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco, structureDefinition)); - - return result; - } - - /// - /// Validate an instance serialized in xml against a given set of profiles and the - /// relevant XSD profile from the FHIR specification. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance, IEnumerable structureDefinitions) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco, structureDefinitions)); - - return result; - } - - - internal static OperationOutcome ValidatedParseXml(this Validator me, XmlReader instance, XmlSchemaSet xsdSchemas, out Resource? poco) - { - var result = new OperationOutcome(); - - try - { - - if (me.Settings.EnableXsdValidation) - { - var doc = XDocument.Load(instance, LoadOptions.SetLineInfo); - result.Add(validateXml(doc, xsdSchemas)); - instance = doc.CreateReader(); - } - - poco = (Resource)(new FhirXmlParser()).Parse(instance, typeof(Resource)); - } - catch (Exception e) - { - result.AddIssue($"Parsing of Xml into a FHIR Poco failed: {e.Message}", Issue.XSD_CONTENT_POCO_PARSING_FAILED, default(string)); - poco = null; - } - - return result; - - static OperationOutcome validateXml(XDocument instance, XmlSchemaSet xsdSchemas) - { - var result = new OperationOutcome(); - - instance.Validate(xsdSchemas, (o, args) => { result.AddIssue(toIssueComponent(args)); }); - - return result; - } - - } - - - private static OperationOutcome.IssueComponent toIssueComponent(ValidationEventArgs args) - { - string message = $".NET Xsd validation {args.Severity}: {args.Message}"; - string pos = $"line: {args.Exception.LineNumber}, pos: {args.Exception.LinePosition}"; - - return args.Severity == XmlSeverityType.Error - ? Issue.XSD_VALIDATION_ERROR.ToIssueComponent(message, pos) - : Issue.XSD_VALIDATION_WARNING.ToIssueComponent(message, pos); - } - } -} - -#nullable restore \ No newline at end of file diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index 531500e8..4729a44a 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -1,9 +1,8 @@ - + - + Exe - net6.0 Benchmarks Firely.Fhir.Validation.Benchmarks enable @@ -75,8 +74,7 @@ - - + diff --git a/test/Benchmarks/ValidatorBenchmarks.cs b/test/Benchmarks/ValidatorBenchmarks.cs index a2fa473e..5b7c2050 100644 --- a/test/Benchmarks/ValidatorBenchmarks.cs +++ b/test/Benchmarks/ValidatorBenchmarks.cs @@ -6,11 +6,10 @@ using Hl7.Fhir.Specification; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Specification.Terminology; -using Hl7.Fhir.Validation; using System; using System.Diagnostics; using System.IO; -using System.Linq; +using System.Threading.Tasks; //using Validator = Hl7.Fhir.Validation.Validator; namespace Firely.Sdk.Benchmarks @@ -22,11 +21,9 @@ public class ValidatorBenchmarks private static readonly IStructureDefinitionSummaryProvider PROVIDER = new StructureDefinitionSummaryProvider(ZIPSOURCE); private static readonly string TEST_DIRECTORY = Path.GetFullPath(@"TestData\DocumentComposition"); - public ITypedElement? TestResource = null; + public ElementNode? TestResource = null; public string? InstanceTypeProfile = null; - public IElementSchemaResolver? SchemaResolver = null; public IResourceResolver? TestResolver = null; - public ElementSchema? TestSchema = null; [GlobalSetup] public void GlobalSetup() @@ -34,18 +31,16 @@ public void GlobalSetup() //var testResourceData = File.ReadAllText(Path.Combine(TEST_DIRECTORY, "Levin.patient.xml")); var testResourceData = File.ReadAllText(Path.Combine(TEST_DIRECTORY, "MainBundle.bundle.xml")); - TestResource = FhirXmlNode.Parse(testResourceData).ToTypedElement(PROVIDER)!; + TestResource = ElementNode.FromElement(FhirXmlNode.Parse(testResourceData).ToTypedElement(PROVIDER)!); //InstanceTypeProfile = Hl7.Fhir.Model.ModelInfo.CanonicalUriForFhirCoreType(TestResource.InstanceType).Value!; InstanceTypeProfile = "http://example.org/StructureDefinition/DocumentBundle"; var testFilesResolver = new DirectorySource(TEST_DIRECTORY); TestResolver = new CachedResolver(new SnapshotSource(new CachedResolver(new MultiResolver(testFilesResolver, ZIPSOURCE))))!; - SchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(TestResolver.AsAsync()!); - TestSchema = SchemaResolver.GetSchema(InstanceTypeProfile); // To avoid warnings about bi-model distributions, run the (slow) first-time run here in setup - var cold = validateWip(TestResource!, TestSchema!, TestResolver!, SchemaResolver!); - Debug.Assert(cold.IsSuccessful); + var cold = validateWip(TestResource!, InstanceTypeProfile!, TestResolver!); + Debug.Assert(cold.Success); var oldCold = validateCurrent(TestResource!, InstanceTypeProfile!, TestResolver!); Debug.Assert(oldCold.Success); @@ -60,25 +55,16 @@ public void CurrentValidator() [Benchmark] public void WipValidator() { - _ = validateWip(TestResource!, TestSchema!, TestResolver!, SchemaResolver!); + _ = validateWip(TestResource!, InstanceTypeProfile!, TestResolver!); } - private static ResultReport validateWip(ITypedElement typedElement, ElementSchema schema, IResourceResolver arr, IElementSchemaResolver schemaResolver) + private static Hl7.Fhir.Model.OperationOutcome validateWip(ElementNode typedElement, string schema, IResourceResolver rr) { - var constraintsToBeIgnored = new string[] { "rng-2", "dom-6" }; - var validationContext = new ValidationContext(schemaResolver, new LocalTerminologyService(arr.AsAsync())) - { - ResolveExternalReference = (u, _) => arr.ResolveByUri(u)?.ToTypedElement(), - // IncludeFilter = Settings.SkipConstraintValidation ? (Func)(a => !(a is FhirPathAssertion)) : (Func)null, - // 20190703 Issue 447 - rng-2 is incorrect in DSTU2 and STU3. EK - // should be removed from STU3/R4 once we get the new normative version - // of FP up, which could do comparisons between quantities. - // 2022-01-19 MS: added best practice constraint "dom-6" to be ignored, which checks if a resource has a narrative. - ExcludeFilter = a => a is FhirPathValidator fhirPathAssertion && constraintsToBeIgnored.Contains(fhirPathAssertion.Key) - }; - - typedElement = ElementNode.FromElement(typedElement); - var result = schema!.Validate(typedElement, validationContext); + var arr = rr.AsAsync(); + var ts = new LocalTerminologyService(arr); + + var validator = new Validator(arr, ts, new TestExternalReferenceResolver(rr)); + var result = validator.Validate(typedElement, schema); return result; } @@ -101,5 +87,16 @@ private static Hl7.Fhir.Model.OperationOutcome validateCurrent(ITypedElement typ //return outcome; } + private class TestExternalReferenceResolver : IExternalReferenceResolver + { + public TestExternalReferenceResolver(IResourceResolver resolver) + { + Resolver = resolver; + } + + public IResourceResolver Resolver { get; } + + public Task ResolveAsync(string reference) => Task.FromResult((object?)Resolver.ResolveByUri(reference)); + } } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj b/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj index 0de44997..bc096fb2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj @@ -23,5 +23,6 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json index 7c66a605..e6e49f87 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "BackboneElement", "abstract": true, "derivation": "specialization" diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json index f75e7770..4d5a1ab0 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Composition", - "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Composition", "abstract": false, "derivation": "specialization" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Composition.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -100,18 +100,37 @@ "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Extension" }, + "url": { + "id": "#Composition.url", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/uri" + }, "identifier": { "id": "#Composition.identifier", "FastInvariant-ele1": {}, - "cardinality": "0..1", + "fhirPath-ident-1": { + "key": "ident-1", + "expression": "value.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Identifier with no value has limited utility. If communicating that an identifier value has been suppressed or missing, the value element SHOULD be present with an extension indicating the missing semantic - e.g. data-absent-reason" + }, + "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, + "version": { + "id": "#Composition.version", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "status": { "id": "#Composition.status", "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/composition-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/composition-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -133,7 +152,7 @@ "binding": { "abstractAllowed": true, "strength": "example", - "valueSet": "http://hl7.org/fhir/ValueSet/document-classcodes" + "valueSet": "http://hl7.org/fhir/ValueSet/referenced-item-category" }, "FastInvariant-ele1": {}, "cardinality": "0..*", @@ -144,12 +163,19 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, - "cardinality": "0..1", + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, + "cardinality": "0..*", "allOf": [ { "ref": "http://hl7.org/fhir/StructureDefinition/Reference" @@ -168,11 +194,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -193,16 +226,29 @@ "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/dateTime" }, + "useContext": { + "id": "#Composition.useContext", + "FastInvariant-ele1": {}, + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/UsageContext" + }, "author": { "id": "#Composition.author", "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "1..*", "allOf": [ { @@ -284,22 +330,23 @@ } ] }, + "name": { + "id": "#Composition.name", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "title": { "id": "#Composition.title", "FastInvariant-ele1": {}, "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, - "confidentiality": { - "id": "#Composition.confidentiality", - "binding": { - "abstractAllowed": true, - "strength": "required", - "valueSet": "http://terminology.hl7.org/ValueSet/v3-ConfidentialityClassification|2014-03-26" - }, + "note": { + "id": "#Composition.note", "FastInvariant-ele1": {}, - "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/Annotation" }, "attester": { "id": "#Composition.attester", @@ -329,12 +376,12 @@ "id": "#Composition.attester.mode", "binding": { "abstractAllowed": true, - "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/composition-attestation-mode|4.0.1" + "strength": "preferred", + "valueSet": "http://hl7.org/fhir/ValueSet/composition-attestation-mode" }, "FastInvariant-ele1": {}, "cardinality": "1..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" }, "time": { "id": "#Composition.attester.time", @@ -347,11 +394,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -431,11 +485,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -454,86 +515,7 @@ "id": "#Composition.relatesTo", "FastInvariant-ele1": {}, "cardinality": "0..*", - "children": { - "id": { - "id": "#Composition.relatesTo.id", - "cardinality": "0..1", - "ref": "http://hl7.org/fhirpath/System.String" - }, - "extension": { - "id": "#Composition.relatesTo.extension", - "FastInvariant-ele1": {}, - "FastInvariant-ext1": {}, - "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/Extension" - }, - "modifierExtension": { - "id": "#Composition.relatesTo.modifierExtension", - "FastInvariant-ele1": {}, - "FastInvariant-ext1": {}, - "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/Extension" - }, - "code": { - "id": "#Composition.relatesTo.code", - "binding": { - "abstractAllowed": true, - "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/document-relationship-type|4.0.1" - }, - "FastInvariant-ele1": {}, - "cardinality": "1..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" - }, - "target[x]": { - "id": "#Composition.relatesTo.target[x]", - "FastInvariant-ele1": {}, - "cardinality": "1..1", - "slice": { - "ordered": false, - "defaultAtEnd": false, - "case": [ - { - "name": "Identifier", - "condition": { - "fhir-type-label": "Identifier" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" - } - }, - { - "name": "Reference", - "condition": { - "fhir-type-label": "Reference" - }, - "assertion": { - "allOf": [ - { - "ref": "http://hl7.org/fhir/StructureDefinition/Reference" - }, - { - "validate": { - "schema": { - "ref": "http://hl7.org/fhir/StructureDefinition/Composition" - } - } - } - ] - } - } - ], - "default": { - "issue": { - "issueNumber": 1011, - "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Identifier','Reference')", - "type": "Invalid" - } - } - } - } - } + "ref": "http://hl7.org/fhir/StructureDefinition/RelatedArtifact" }, "event": { "id": "#Composition.event", @@ -559,26 +541,15 @@ "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Extension" }, - "code": { - "id": "#Composition.event.code", - "binding": { - "abstractAllowed": true, - "strength": "example", - "valueSet": "http://terminology.hl7.org/ValueSet/v3-ActCode" - }, - "FastInvariant-ele1": {}, - "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" - }, "period": { "id": "#Composition.event.period", "FastInvariant-ele1": {}, "fhirPath-per-1": { "key": "per-1", - "expression": "start.hasValue().not() or end.hasValue().not() or (start <= end)", + "expression": "start.hasValue().not() or end.hasValue().not() or (start.lowBoundary() <= end.highBoundary())", "severity": "error", "bestPractice": false, - "humanDescription": "If present, start SHALL have a lower value than end" + "humanDescription": "If present, start SHALL have a lower or equal value than end" }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Period" @@ -586,17 +557,10 @@ "detail": { "id": "#Composition.event.detail", "FastInvariant-ele1": {}, - "fhirPath-ref-1": { - "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", - "severity": "error", - "bestPractice": false, - "humanDescription": "SHALL have a contained resource if a local reference is provided" - }, "cardinality": "0..*", "allOf": [ { - "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableReference" }, { "validate": { @@ -675,11 +639,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..*", "allOf": [ { @@ -766,11 +737,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -791,17 +769,6 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Narrative" }, - "mode": { - "id": "#Composition.section.mode", - "binding": { - "abstractAllowed": true, - "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/list-mode|4.0.1" - }, - "FastInvariant-ele1": {}, - "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" - }, "orderedBy": { "id": "#Composition.section.orderedBy", "binding": { @@ -818,11 +785,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..*", "allOf": [ { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json index 419ed03a..88afb049 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json @@ -4,7 +4,7 @@ "schema-subtype": "extension", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Extension", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/DataType,http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Extension", "abstract": false, "derivation": "specialization" @@ -130,6 +130,15 @@ "ref": "http://hl7.org/fhir/StructureDefinition/integer" } }, + { + "name": "integer64", + "condition": { + "fhir-type-label": "integer64" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/integer64" + } + }, { "name": "markdown", "condition": { @@ -256,6 +265,26 @@ "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" } }, + { + "name": "CodeableReference", + "condition": { + "fhir-type-label": "CodeableReference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableReference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/Resource" + } + } + } + ] + } + }, { "name": "Coding", "condition": { @@ -364,6 +393,15 @@ "ref": "http://hl7.org/fhir/StructureDefinition/Ratio" } }, + { + "name": "RatioRange", + "condition": { + "fhir-type-label": "RatioRange" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/RatioRange" + } + }, { "name": "Reference", "condition": { @@ -420,15 +458,6 @@ "ref": "http://hl7.org/fhir/StructureDefinition/ContactDetail" } }, - { - "name": "Contributor", - "condition": { - "fhir-type-label": "Contributor" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Contributor" - } - }, { "name": "DataRequirement", "condition": { @@ -483,6 +512,24 @@ "ref": "http://hl7.org/fhir/StructureDefinition/UsageContext" } }, + { + "name": "Availability", + "condition": { + "fhir-type-label": "Availability" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Availability" + } + }, + { + "name": "ExtendedContactDetail", + "condition": { + "fhir-type-label": "ExtendedContactDetail" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/ExtendedContactDetail" + } + }, { "name": "Dosage", "condition": { @@ -506,7 +553,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('base64Binary','boolean','canonical','code','date','dateTime','decimal','id','instant','integer','markdown','oid','positiveInt','string','time','unsignedInt','uri','url','uuid','Address','Age','Annotation','Attachment','CodeableConcept','Coding','ContactPoint','Count','Distance','Duration','HumanName','Identifier','Money','Period','Quantity','Range','Ratio','Reference','SampledData','Signature','Timing','ContactDetail','Contributor','DataRequirement','Expression','ParameterDefinition','RelatedArtifact','TriggerDefinition','UsageContext','Dosage','Meta')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('base64Binary','boolean','canonical','code','date','dateTime','decimal','id','instant','integer','integer64','markdown','oid','positiveInt','string','time','unsignedInt','uri','url','uuid','Address','Age','Annotation','Attachment','CodeableConcept','CodeableReference','Coding','ContactPoint','Count','Distance','Duration','HumanName','Identifier','Money','Period','Quantity','Range','Ratio','RatioRange','Reference','SampledData','Signature','Timing','ContactDetail','DataRequirement','Expression','ParameterDefinition','RelatedArtifact','TriggerDefinition','UsageContext','Availability','ExtendedContactDetail','Dosage','Meta')", "type": "Invalid" } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json index d5277427..6d5cb35e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Patient", - "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Patient", "abstract": false, "derivation": "specialization" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Patient.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -103,6 +103,13 @@ "identifier": { "id": "#Patient.identifier", "FastInvariant-ele1": {}, + "fhirPath-ident-1": { + "key": "ident-1", + "expression": "value.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Identifier with no value has limited utility. If communicating that an identifier value has been suppressed or missing, the value element SHOULD be present with an extension indicating the missing semantic - e.g. data-absent-reason" + }, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, @@ -136,7 +143,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -324,7 +331,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -335,11 +342,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -359,10 +373,10 @@ "FastInvariant-ele1": {}, "fhirPath-per-1": { "key": "per-1", - "expression": "start.hasValue().not() or end.hasValue().not() or (start <= end)", + "expression": "start.hasValue().not() or end.hasValue().not() or (start.lowBoundary() <= end.highBoundary())", "severity": "error", "bestPractice": false, - "humanDescription": "If present, start SHALL have a lower value than end" + "humanDescription": "If present, start SHALL have a lower or equal value than end" }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Period" @@ -397,8 +411,8 @@ "id": "#Patient.communication.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -417,11 +431,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..*", "allOf": [ { @@ -481,11 +502,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -529,11 +557,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "1..1", "allOf": [ { @@ -584,7 +619,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/link-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/link-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json index ba64e65b..cdada6a9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/PatternSliceTestcase", - "base": "http://hl7.org/fhir/StructureDefinition/Patient,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Patient,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Patient", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Patient.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -141,7 +141,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -243,7 +243,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -338,7 +338,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -512,7 +512,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -571,8 +571,8 @@ "id": "#Patient.communication.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -737,7 +737,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/link-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/link-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json index 787dc377..439b7313 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/ProfiledFlag", - "base": "http://hl7.org/fhir/StructureDefinition/Flag,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Flag,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Flag", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Flag.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -111,7 +111,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/flag-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/flag-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -267,6 +267,15 @@ "ref": "http://hl7.org/fhir/StructureDefinition/Patient" } }, + { + "name": "forRelatedPerson", + "condition": { + "fhir-type-label": "RelatedPerson" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/RelatedPerson" + } + }, { "name": "forPractitioner", "condition": { @@ -290,7 +299,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json index d6704114..ff40477f 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/ProfiledObservation", - "base": "http://hl7.org/fhir/StructureDefinition/Observation,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Observation,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Observation", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -58,7 +58,14 @@ "expression": "value.empty() or component.code.where(coding.intersect(%resource.code.coding).exists()).empty()", "severity": "error", "bestPractice": false, - "humanDescription": "If Observation.code is the same as an Observation.component.code then the value element associated with the code SHALL NOT be present" + "humanDescription": "If Observation.component.code is the same as Observation.code, then Observation.value SHALL NOT be present (the Observation.component.value[x] holds the value)." + }, + "fhirPath-obs-8": { + "key": "obs-8", + "expression": "bodySite.exists() implies bodyStructure.empty()", + "severity": "error", + "bestPractice": false, + "humanDescription": "bodyStructure SHALL only be present if Observation.bodySite is not present" }, "children": { "id": { @@ -82,8 +89,8 @@ "id": "#Observation.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -120,6 +127,54 @@ "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, + "instantiates[x]": { + "id": "#Observation.instantiates[x]", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "canonical", + "condition": { + "fhir-type-label": "canonical" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/canonical" + } + }, + { + "name": "Reference", + "condition": { + "fhir-type-label": "Reference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/ObservationDefinition" + } + } + } + ] + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('canonical','Reference')", + "type": "Invalid" + } + } + } + }, "basedOn": { "id": "#Observation.basedOn", "FastInvariant-ele1": {}, @@ -204,6 +259,66 @@ } ] }, + "triggeredBy": { + "id": "#Observation.triggeredBy", + "FastInvariant-ele1": {}, + "cardinality": "0..*", + "children": { + "id": { + "id": "#Observation.triggeredBy.id", + "cardinality": "0..1", + "ref": "http://hl7.org/fhirpath/System.String" + }, + "extension": { + "id": "#Observation.triggeredBy.extension", + "FastInvariant-ele1": {}, + "FastInvariant-ext1": {}, + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/Extension" + }, + "modifierExtension": { + "id": "#Observation.triggeredBy.modifierExtension", + "FastInvariant-ele1": {}, + "FastInvariant-ext1": {}, + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/Extension" + }, + "observation": { + "id": "#Observation.triggeredBy.observation", + "FastInvariant-ele1": {}, + "cardinality": "1..1", + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/Observation" + } + } + } + ] + }, + "type": { + "id": "#Observation.triggeredBy.type", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/observation-triggeredbytype|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "1..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, + "reason": { + "id": "#Observation.triggeredBy.reason", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + } + } + }, "partOf": { "id": "#Observation.partOf", "FastInvariant-ele1": {}, @@ -272,13 +387,22 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/ImagingStudy" } + }, + { + "name": "forGenomicStudy", + "condition": { + "fhir-type-label": "GenomicStudy" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/GenomicStudy" + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/MedicationAdministration, http://hl7.org/fhir/StructureDefinition/MedicationDispense, http://hl7.org/fhir/StructureDefinition/MedicationStatement, http://hl7.org/fhir/StructureDefinition/Procedure, http://hl7.org/fhir/StructureDefinition/Immunization, http://hl7.org/fhir/StructureDefinition/ImagingStudy). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/MedicationAdministration, http://hl7.org/fhir/StructureDefinition/MedicationDispense, http://hl7.org/fhir/StructureDefinition/MedicationStatement, http://hl7.org/fhir/StructureDefinition/Procedure, http://hl7.org/fhir/StructureDefinition/Immunization, http://hl7.org/fhir/StructureDefinition/ImagingStudy, http://hl7.org/fhir/StructureDefinition/GenomicStudy). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -293,7 +417,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/observation-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/observation-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -682,13 +806,42 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/Period" } + }, + { + "name": "Attachment", + "condition": { + "fhir-type-label": "Attachment" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" + } + }, + { + "name": "Reference", + "condition": { + "fhir-type-label": "Reference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/MolecularSequence" + } + } + } + ] + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period','Attachment','Reference')", "type": "Invalid" } } @@ -733,6 +886,23 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" }, + "bodyStructure": { + "id": "#Observation.bodyStructure", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/BodyStructure" + } + } + } + ] + }, "method": { "id": "#Observation.method", "binding": { @@ -747,6 +917,13 @@ "specimen": { "id": "#Observation.specimen", "FastInvariant-ele1": {}, + "fhirPath-obs-9": { + "key": "obs-9", + "expression": "(reference.resolve().exists() and reference.resolve() is Group) implies reference.resolve().member.entity.resolve().all($this is Specimen)", + "severity": "error", + "bestPractice": false, + "humanDescription": "If Observation.specimen is a reference to Group, the group can only have specimens" + }, "cardinality": "0..1", "allOf": [ { @@ -755,7 +932,38 @@ { "validate": { "schema": { - "ref": "http://hl7.org/fhir/StructureDefinition/Specimen" + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "forSpecimen", + "condition": { + "fhir-type-label": "Specimen" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Specimen" + } + }, + { + "name": "forGroup", + "condition": { + "fhir-type-label": "Group" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Group" + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Specimen, http://hl7.org/fhir/StructureDefinition/Group). None of these are profiles on type %INSTANCETYPE% of the resource.", + "type": "Invalid" + } + } + } } } } @@ -852,6 +1060,17 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/SimpleQuantity" }, + "normalValue": { + "id": "#Observation.referenceRange.normalValue", + "binding": { + "abstractAllowed": true, + "strength": "extensible", + "valueSet": "http://hl7.org/fhir/ValueSet/observation-referencerange-normalvalue" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" + }, "type": { "id": "#Observation.referenceRange.type", "binding": { @@ -884,7 +1103,7 @@ "id": "#Observation.referenceRange.text", "FastInvariant-ele1": {}, "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/string" + "ref": "http://hl7.org/fhir/StructureDefinition/markdown" } } }, @@ -979,12 +1198,12 @@ } }, { - "name": "forMedia", + "name": "forImagingSelection", "condition": { - "fhir-type-label": "Media" + "fhir-type-label": "ImagingSelection" }, "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Media" + "ref": "http://hl7.org/fhir/StructureDefinition/ImagingSelection" } }, { @@ -1013,13 +1232,22 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/MolecularSequence" } + }, + { + "name": "forGenomicStudy", + "condition": { + "fhir-type-label": "GenomicStudy" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/GenomicStudy" + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/DocumentReference, http://hl7.org/fhir/StructureDefinition/ImagingStudy, http://hl7.org/fhir/StructureDefinition/Media, http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse, http://hl7.org/fhir/StructureDefinition/Observation, http://hl7.org/fhir/StructureDefinition/MolecularSequence). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/DocumentReference, http://hl7.org/fhir/StructureDefinition/ImagingStudy, http://hl7.org/fhir/StructureDefinition/ImagingSelection, http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse, http://hl7.org/fhir/StructureDefinition/Observation, http://hl7.org/fhir/StructureDefinition/MolecularSequence, http://hl7.org/fhir/StructureDefinition/GenomicStudy). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -1170,13 +1398,42 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/Period" } + }, + { + "name": "Attachment", + "condition": { + "fhir-type-label": "Attachment" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" + } + }, + { + "name": "Reference", + "condition": { + "fhir-type-label": "Reference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/MolecularSequence" + } + } + } + ] + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period','Attachment','Reference')", "type": "Invalid" } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json index 94e66236..05023009 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json @@ -4,51 +4,16 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/ProfiledBackboneAndContentref", - "base": "http://hl7.org/fhir/StructureDefinition/Questionnaire,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Questionnaire,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Questionnaire", "abstract": false, "derivation": "constraint" } }, "fhir-type-label": "Questionnaire", - "fhirPath-dom-2": { - "key": "dom-2", - "expression": "contained.contained.empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If the resource is contained in another resource, it SHALL NOT contain nested Resources" - }, - "fhirPath-dom-3": { - "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" - }, - "fhirPath-dom-4": { - "key": "dom-4", - "expression": "contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated" - }, - "fhirPath-dom-5": { - "key": "dom-5", - "expression": "contained.meta.security.empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If a resource is contained in another resource, it SHALL NOT have a security label" - }, - "fhirPath-dom-6": { - "key": "dom-6", - "expression": "text.`div`.exists()", - "severity": "warning", - "bestPractice": true, - "humanDescription": "A resource should have narrative for robust management" - }, - "fhirPath-que-0": { - "key": "que-0", - "expression": "name.matches('[A-Z]([A-Za-z0-9_]){0,254}')", + "fhirPath-cnl-0": { + "key": "cnl-0", + "expression": "name.exists() implies name.matches('^[A-Z]([A-Za-z0-9_]){1,254}$')", "severity": "warning", "bestPractice": false, "humanDescription": "Name should be usable as an identifier for the module by machine processing applications such as code generation" @@ -82,8 +47,8 @@ "id": "#Questionnaire.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -116,6 +81,13 @@ }, "url": { "id": "#Questionnaire.url", + "fhirPath-cnl-1": { + "key": "cnl-1", + "expression": "exists() implies matches('^[^|# ]+$')", + "severity": "warning", + "bestPractice": false, + "humanDescription": "URL should not contain | or # - these characters make processing canonical references problematic" + }, "FastInvariant-ele1": {}, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/uri" @@ -132,6 +104,48 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, + "versionAlgorithm[x]": { + "id": "#Questionnaire.versionAlgorithm[x]", + "binding": { + "abstractAllowed": true, + "strength": "extensible", + "valueSet": "http://hl7.org/fhir/ValueSet/version-algorithm" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "string", + "condition": { + "fhir-type-label": "string" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/string" + } + }, + { + "name": "Coding", + "condition": { + "fhir-type-label": "Coding" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Coding" + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('string','Coding')", + "type": "Invalid" + } + } + } + }, "name": { "id": "#Questionnaire.name", "FastInvariant-ele1": {}, @@ -155,7 +169,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -172,7 +186,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..*", @@ -231,6 +245,12 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/markdown" }, + "copyrightLabel": { + "id": "#Questionnaire.copyrightLabel", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "approvalDate": { "id": "#Questionnaire.approvalDate", "FastInvariant-ele1": {}, @@ -263,12 +283,26 @@ "item": { "id": "#Questionnaire.item", "FastInvariant-ele1": {}, - "fhirPath-que-1": { - "key": "que-1", - "expression": "(type='group' implies item.empty().not()) and (type.trace('type')='display' implies item.trace('item').empty())", + "fhirPath-que-1a": { + "key": "que-1a", + "expression": "(type='group' and %resource.status='complete') implies item.empty().not()", + "severity": "error", + "bestPractice": false, + "humanDescription": "Group items must have nested items when Questionanire is complete" + }, + "fhirPath-que-1b": { + "key": "que-1b", + "expression": "type='group' implies item.empty().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Groups should have items" + }, + "fhirPath-que-1c": { + "key": "que-1c", + "expression": "type='display' implies item.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "Group items must have nested items, display items cannot have nested items" + "humanDescription": "Display items cannot have child items" }, "fhirPath-que-3": { "key": "que-3", @@ -286,10 +320,10 @@ }, "fhirPath-que-5": { "key": "que-5", - "expression": "(type ='choice' or type = 'open-choice' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", + "expression": "(type='coding' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", "severity": "error", "bestPractice": false, - "humanDescription": "Only 'choice' and 'open-choice' items can have answerValueSet" + "humanDescription": "Only coding, decimal, integer, date, dateTime, time, string or quantity items can have answerOption or answerValueSet" }, "fhirPath-que-6": { "key": "que-6", @@ -314,7 +348,7 @@ }, "fhirPath-que-10": { "key": "que-10", - "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url' | 'open-choice')) or maxLength.empty()", + "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url')) or answerConstraint='optionOrString' or maxLength.empty()", "severity": "error", "bestPractice": false, "humanDescription": "Maximum length can only be declared for simple question types" @@ -324,11 +358,11 @@ "expression": "answerOption.empty() or initial.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "If one or more answerOption is present, initial[x] must be missing" + "humanDescription": "If one or more answerOption is present, initial cannot be present. Use answerOption.initialSelected instead" }, "fhirPath-que-12": { "key": "que-12", - "expression": "enableWhen.count() > 2 implies enableBehavior.exists()", + "expression": "enableWhen.count() > 1 implies enableBehavior.exists()", "severity": "error", "bestPractice": false, "humanDescription": "If there are more than one enableWhen, enableBehavior must be specified" @@ -340,6 +374,13 @@ "bestPractice": false, "humanDescription": "Can only have multiple initial values for repeating items" }, + "fhirPath-que-14": { + "key": "que-14", + "expression": "answerConstraint.exists() implies answerOption.exists() or answerValueSet.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Can only have answerConstraint if answerOption or answerValueSet are present. (This is a warning because extensions may serve the same purpose)" + }, "cardinality": "1..100", "children": { "id": { @@ -364,6 +405,13 @@ "linkId": { "id": "#Questionnaire.item.linkId", "FastInvariant-ele1": {}, + "fhirPath-que-15": { + "key": "que-15", + "expression": "$this.length() <= 255", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Link ids should be 255 characters or less" + }, "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, @@ -401,7 +449,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/item-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/item-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -449,7 +497,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -587,7 +635,18 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, + "disabledDisplay": { + "id": "#Questionnaire.item.disabledDisplay", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-disabled-display|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -617,6 +676,17 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/integer" }, + "answerConstraint": { + "id": "#Questionnaire.item.answerConstraint", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-answer-constraint|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, "answerValueSet": { "id": "#Questionnaire.item.answerValueSet", "FastInvariant-ele1": {}, diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json index 3da46bdd..be36e6e5 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Questionnaire", - "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Questionnaire", "abstract": false, "derivation": "specialization" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -46,6 +46,13 @@ "bestPractice": true, "humanDescription": "A resource should have narrative for robust management" }, + "fhirPath-cnl-0": { + "key": "cnl-0", + "expression": "name.exists() implies name.matches('^[A-Z]([A-Za-z0-9_]){1,254}$')", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Name should be usable as an identifier for the module by machine processing applications such as code generation" + }, "fhirPath-que-2": { "key": "que-2", "expression": "descendants().linkId.isDistinct()", @@ -53,13 +60,6 @@ "bestPractice": false, "humanDescription": "The link ids for groups and questions must be unique within the questionnaire" }, - "fhirPath-que-0": { - "key": "que-0", - "expression": "name.matches('[A-Z]([A-Za-z0-9_]){0,254}')", - "severity": "warning", - "bestPractice": false, - "humanDescription": "Name should be usable as an identifier for the module by machine processing applications such as code generation" - }, "children": { "id": { "id": "#Questionnaire.id", @@ -82,8 +82,8 @@ "id": "#Questionnaire.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -117,12 +117,26 @@ "url": { "id": "#Questionnaire.url", "FastInvariant-ele1": {}, + "fhirPath-cnl-1": { + "key": "cnl-1", + "expression": "exists() implies matches('^[^|# ]+$')", + "severity": "warning", + "bestPractice": false, + "humanDescription": "URL should not contain | or # - these characters make processing canonical references problematic" + }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/uri" }, "identifier": { "id": "#Questionnaire.identifier", "FastInvariant-ele1": {}, + "fhirPath-ident-1": { + "key": "ident-1", + "expression": "value.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Identifier with no value has limited utility. If communicating that an identifier value has been suppressed or missing, the value element SHOULD be present with an extension indicating the missing semantic - e.g. data-absent-reason" + }, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, @@ -132,6 +146,48 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, + "versionAlgorithm[x]": { + "id": "#Questionnaire.versionAlgorithm[x]", + "binding": { + "abstractAllowed": true, + "strength": "extensible", + "valueSet": "http://hl7.org/fhir/ValueSet/version-algorithm" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "string", + "condition": { + "fhir-type-label": "string" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/string" + } + }, + { + "name": "Coding", + "condition": { + "fhir-type-label": "Coding" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Coding" + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('string','Coding')", + "type": "Invalid" + } + } + } + }, "name": { "id": "#Questionnaire.name", "FastInvariant-ele1": {}, @@ -155,7 +211,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -172,7 +228,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..*", @@ -231,6 +287,12 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/markdown" }, + "copyrightLabel": { + "id": "#Questionnaire.copyrightLabel", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "approvalDate": { "id": "#Questionnaire.approvalDate", "FastInvariant-ele1": {}, @@ -248,10 +310,10 @@ "FastInvariant-ele1": {}, "fhirPath-per-1": { "key": "per-1", - "expression": "start.hasValue().not() or end.hasValue().not() or (start <= end)", + "expression": "start.hasValue().not() or end.hasValue().not() or (start.lowBoundary() <= end.highBoundary())", "severity": "error", "bestPractice": false, - "humanDescription": "If present, start SHALL have a lower value than end" + "humanDescription": "If present, start SHALL have a lower or equal value than end" }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Period" @@ -263,6 +325,13 @@ "strength": "example", "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-questions" }, + "fhirPath-cod-1": { + "key": "cod-1", + "expression": "code.exists().not() implies display.exists().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "A Coding SHOULD NOT have a display unless a code is also present. Computation on Coding.display alone is generally unsafe. Consider using CodeableConcept.text" + }, "FastInvariant-ele1": {}, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Coding" @@ -293,10 +362,10 @@ }, "fhirPath-que-5": { "key": "que-5", - "expression": "(type ='choice' or type = 'open-choice' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", + "expression": "(type='coding' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", "severity": "error", "bestPractice": false, - "humanDescription": "Only 'choice' and 'open-choice' items can have answerValueSet" + "humanDescription": "Only coding, decimal, integer, date, dateTime, time, string or quantity items can have answerOption or answerValueSet" }, "fhirPath-que-4": { "key": "que-4", @@ -312,19 +381,33 @@ "bestPractice": false, "humanDescription": "Display items cannot have a \"code\" asserted" }, - "fhirPath-que-10": { - "key": "que-10", - "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url' | 'open-choice')) or maxLength.empty()", + "fhirPath-que-1c": { + "key": "que-1c", + "expression": "type='display' implies item.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "Maximum length can only be declared for simple question types" + "humanDescription": "Display items cannot have child items" }, - "fhirPath-que-1": { - "key": "que-1", - "expression": "(type='group' implies item.empty().not()) and (type.trace('type')='display' implies item.trace('item').empty())", + "fhirPath-que-1a": { + "key": "que-1a", + "expression": "(type='group' and %resource.status='complete') implies item.empty().not()", "severity": "error", "bestPractice": false, - "humanDescription": "Group items must have nested items, display items cannot have nested items" + "humanDescription": "Group items must have nested items when Questionanire is complete" + }, + "fhirPath-que-1b": { + "key": "que-1b", + "expression": "type='group' implies item.empty().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Groups should have items" + }, + "fhirPath-que-10": { + "key": "que-10", + "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url')) or answerConstraint='optionOrString' or maxLength.empty()", + "severity": "error", + "bestPractice": false, + "humanDescription": "Maximum length can only be declared for simple question types" }, "fhirPath-que-13": { "key": "que-13", @@ -333,16 +416,23 @@ "bestPractice": false, "humanDescription": "Can only have multiple initial values for repeating items" }, + "fhirPath-que-14": { + "key": "que-14", + "expression": "answerConstraint.exists() implies answerOption.exists() or answerValueSet.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Can only have answerConstraint if answerOption or answerValueSet are present. (This is a warning because extensions may serve the same purpose)" + }, "fhirPath-que-11": { "key": "que-11", "expression": "answerOption.empty() or initial.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "If one or more answerOption is present, initial[x] must be missing" + "humanDescription": "If one or more answerOption is present, initial cannot be present. Use answerOption.initialSelected instead" }, "fhirPath-que-12": { "key": "que-12", - "expression": "enableWhen.count() > 2 implies enableBehavior.exists()", + "expression": "enableWhen.count() > 1 implies enableBehavior.exists()", "severity": "error", "bestPractice": false, "humanDescription": "If there are more than one enableWhen, enableBehavior must be specified" @@ -377,6 +467,13 @@ "linkId": { "id": "#Questionnaire.item.linkId", "FastInvariant-ele1": {}, + "fhirPath-que-15": { + "key": "que-15", + "expression": "$this.length() <= 255", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Link ids should be 255 characters or less" + }, "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, @@ -393,6 +490,13 @@ "strength": "example", "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-questions" }, + "fhirPath-cod-1": { + "key": "cod-1", + "expression": "code.exists().not() implies display.exists().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "A Coding SHOULD NOT have a display unless a code is also present. Computation on Coding.display alone is generally unsafe. Consider using CodeableConcept.text" + }, "FastInvariant-ele1": {}, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Coding" @@ -414,7 +518,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/item-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/item-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -462,7 +566,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -600,7 +704,18 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, + "disabledDisplay": { + "id": "#Questionnaire.item.disabledDisplay", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-disabled-display|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -630,6 +745,17 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/integer" }, + "answerConstraint": { + "id": "#Questionnaire.item.answerConstraint", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-answer-constraint|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, "answerValueSet": { "id": "#Questionnaire.item.answerValueSet", "FastInvariant-ele1": {}, diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json index 05d6eba4..0ec983e9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/SecondaryTargetRefSlice", - "base": "http://hl7.org/fhir/StructureDefinition/Communication,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Communication,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Communication", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Communication.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -174,7 +174,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/event-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/event-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -207,7 +207,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/request-priority|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/request-priority|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -344,6 +344,15 @@ "ordered": false, "defaultAtEnd": false, "case": [ + { + "name": "forCareTeam", + "condition": { + "fhir-type-label": "CareTeam" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/CareTeam" + } + }, { "name": "forDevice", "condition": { @@ -353,6 +362,33 @@ "ref": "http://hl7.org/fhir/StructureDefinition/Device" } }, + { + "name": "forGroup", + "condition": { + "fhir-type-label": "Group" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Group" + } + }, + { + "name": "forHealthcareService", + "condition": { + "fhir-type-label": "HealthcareService" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/HealthcareService" + } + }, + { + "name": "forLocation", + "condition": { + "fhir-type-label": "Location" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Location" + } + }, { "name": "forOrganization", "condition": { @@ -399,30 +435,12 @@ } }, { - "name": "forGroup", - "condition": { - "fhir-type-label": "Group" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Group" - } - }, - { - "name": "forCareTeam", - "condition": { - "fhir-type-label": "CareTeam" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/CareTeam" - } - }, - { - "name": "forHealthcareService", + "name": "forEndpoint", "condition": { - "fhir-type-label": "HealthcareService" + "fhir-type-label": "Endpoint" }, "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/HealthcareService" + "ref": "http://hl7.org/fhir/StructureDefinition/Endpoint" } } ], @@ -430,7 +448,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/Group, http://hl7.org/fhir/StructureDefinition/CareTeam, http://hl7.org/fhir/StructureDefinition/HealthcareService). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/CareTeam, http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Group, http://hl7.org/fhir/StructureDefinition/HealthcareService, http://hl7.org/fhir/StructureDefinition/Location, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/Endpoint). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -517,13 +535,31 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/HealthcareService" } + }, + { + "name": "forEndpoint", + "condition": { + "fhir-type-label": "Endpoint" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Endpoint" + } + }, + { + "name": "forCareTeam", + "condition": { + "fhir-type-label": "CareTeam" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/CareTeam" + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/HealthcareService). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/HealthcareService, http://hl7.org/fhir/StructureDefinition/Endpoint, http://hl7.org/fhir/StructureDefinition/CareTeam). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -533,8 +569,8 @@ } ] }, - "reasonCode": { - "id": "#Communication.reasonCode", + "reason": { + "id": "#Communication.reason", "binding": { "abstractAllowed": true, "strength": "example", @@ -542,69 +578,14 @@ }, "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" - }, - "reasonReference": { - "id": "#Communication.reasonReference", - "FastInvariant-ele1": {}, - "cardinality": "0..*", "allOf": [ { - "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableReference" }, { "validate": { "schema": { - "slice": { - "ordered": false, - "defaultAtEnd": false, - "case": [ - { - "name": "forCondition", - "condition": { - "fhir-type-label": "Condition" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Condition" - } - }, - { - "name": "forObservation", - "condition": { - "fhir-type-label": "Observation" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Observation" - } - }, - { - "name": "forDiagnosticReport", - "condition": { - "fhir-type-label": "DiagnosticReport" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/DiagnosticReport" - } - }, - { - "name": "forDocumentReference", - "condition": { - "fhir-type-label": "DocumentReference" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/DocumentReference" - } - } - ], - "default": { - "issue": { - "issueNumber": 1011, - "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Condition, http://hl7.org/fhir/StructureDefinition/Observation, http://hl7.org/fhir/StructureDefinition/DiagnosticReport, http://hl7.org/fhir/StructureDefinition/DocumentReference). None of these are profiles on type %INSTANCETYPE% of the resource.", - "type": "Invalid" - } - } - } + "ref": "http://hl7.org/fhir/StructureDefinition/Resource" } } } @@ -643,12 +624,12 @@ "defaultAtEnd": false, "case": [ { - "name": "string", + "name": "Attachment", "condition": { - "fhir-type-label": "string" + "fhir-type-label": "Attachment" }, "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/string" + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" } }, { @@ -707,7 +688,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('string','Reference')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Attachment','Reference')", "type": "Invalid" } } @@ -719,17 +700,17 @@ "defaultAtEnd": false, "case": [ { - "name": "String", + "name": "Attachment", "condition": { "pathSelector": { "path": "content", "assertion": { - "fhir-type-label": "string" + "fhir-type-label": "Attachment" } } }, "assertion": { - "id": "#Communication.payload:String", + "id": "#Communication.payload:Attachment", "FastInvariant-ele1": {}, "cardinality": "0..*", "children": { @@ -756,7 +737,7 @@ "id": "#Communication.payload.content[x]", "FastInvariant-ele1": {}, "cardinality": "1..1", - "ref": "http://hl7.org/fhir/StructureDefinition/string" + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" } } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json index 6cae7b99..55ec6906 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/SimpleQuantity", - "base": "http://hl7.org/fhir/StructureDefinition/Quantity,http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/Quantity,http://hl7.org/fhir/StructureDefinition/DataType,http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Quantity", "abstract": false, "derivation": "constraint" @@ -50,7 +50,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/quantity-comparator|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..0", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json index 6504e64f..70dd50dd 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/xhtml", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "xhtml", "abstract": false, "derivation": "specialization" diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json index c4ca144d..5599583a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/boolean", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/PrimitiveType,http://hl7.org/fhir/StructureDefinition/DataType,http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "boolean", "abstract": false, "derivation": "specialization" diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj index 747d8f0c..11fc975f 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj @@ -7,7 +7,7 @@ - Firely.Fhir.Validation.Compilation.Tests.STU3 + Firely.Fhir.Validation.Compilation.STU3.Tests @@ -22,5 +22,6 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index 66bc66a6..10e9b07d 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -18,7 +18,6 @@ using System.Linq; using Xunit; using Xunit.Abstractions; -using static Firely.Fhir.Validation.ValidationContext; namespace Firely.Fhir.Validation.Compilation.Tests { @@ -41,15 +40,10 @@ public void OverwriteSchemaSnaps() compareToSchemaSnaps(true); } - - [Fact -#if R5 - (Skip = "TODO: Make this work for R5 as well.") -#endif - ] + [Fact] public void CompareToCorrectSchemaSnaps() { - compareToSchemaSnaps(true); + compareToSchemaSnaps(false); } private void compareToSchemaSnaps(bool overwrite) @@ -340,7 +334,7 @@ internal class SelfDefinedValidator : IValidatable { public JToken ToJson() => new JProperty("selfdefined-validator"); - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => ResultReport.SUCCESS; } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 344f1bdc..2beee2f2 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 344f1bdc7d1c43220a7e68c028ac733172033910 +Subproject commit 2beee2f20efa3f6cc7f8dca43202dc7260e031c1 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs index af4e2ba6..f9e173a9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs @@ -56,7 +56,7 @@ public OperationOutcome Validate(ITypedElement instance, IResourceResolver? reso result.Add(validate(instance, profileUri)); } - outcome.Add(ResultReport.FromEvidence(result) + outcome.Add(ResultReport.Combine(result) .CleanUp() .ToOperationOutcome()); return outcome; @@ -76,7 +76,7 @@ ResultReport validate(ITypedElement typedElement, string canonicalProfile) // should be removed from STU3/R4 once we get the new normative version // of FP up, which could do comparisons between quantities. // 2022-01-19 MS: added best practice constraint "dom-6" to be ignored, which checks if a resource has a narrative. - ExcludeFilter = a => a is FhirPathValidator fhirPathAssertion && constraintsToBeIgnored.Contains(fhirPathAssertion.Key) + ExcludeFilters = new Predicate[] { a => a is FhirPathValidator fhirPathAssertion && constraintsToBeIgnored.Contains(fhirPathAssertion.Key) } }; _stopWatch.Start(); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems index e472bdf7..44aaa82a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems @@ -1805,6 +1805,8 @@ + + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs index 10863361..6701ca14 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs @@ -14,11 +14,11 @@ namespace Firely.Fhir.Validation.Compilation.Tests { public class SchemaBuilderFixture { - public readonly IElementSchemaResolver SchemaResolver; + internal readonly IElementSchemaResolver SchemaResolver; public readonly FhirPathCompiler FpCompiler; public readonly ICodeValidationTerminologyService ValidateCodeService; public readonly IAsyncResourceResolver ResourceResolver; - public readonly SchemaBuilder Builder; + internal readonly SchemaBuilder Builder; public SchemaBuilderFixture() { @@ -40,7 +40,7 @@ public SchemaBuilderFixture() Builder = new SchemaBuilder(ResourceResolver, new[] { new StandardBuilders(ResourceResolver) }); } - public ValidationContext NewValidationContext() => + internal ValidationContext NewValidationContext() => new(SchemaResolver, ValidateCodeService) { FhirPathCompiler = FpCompiler }; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs index 9dc4edc4..fd7d97c0 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation.Compilation.Tests { - public static class SchemaFluentAssertionsExtensions + internal static class SchemaFluentAssertionsExtensions { public static AndConstraint BeASchemaAssertionFor(this ObjectAssertions me, string uri) => me.BeOfType().Which @@ -186,7 +186,7 @@ private static ElementDefinition.TypeRefComponent build(string code, string[]? p #endif #if STU3 - private IAssertion convert(string code, string[]? profiles = null, string[]? targets = null) + private IAssertion? convert(string code, string[]? profiles = null, string[]? targets = null) { IEnumerable typeRefs = profiles switch { @@ -202,14 +202,14 @@ private IAssertion convert(string code, string[]? profiles = null, string[]? tar left.Join(right, x => true, y => true, (l, r) => (l, r)); } #else - private IAssertion convert(string code, string[]? profiles = null, string[]? targets = null) + private IAssertion? convert(string code, string[]? profiles = null, string[]? targets = null) => convertTypeReference(build(code, profiles, targets)); #endif - private IAssertion convert(IEnumerable trs) => + private IAssertion? convert(IEnumerable trs) => new TypeReferenceBuilder(_fixture.ResourceResolver).ConvertTypeReferences(trs); - private IAssertion convertTypeReference(ElementDefinition.TypeRefComponent typeRef) + private IAssertion? convertTypeReference(ElementDefinition.TypeRefComponent typeRef) => new TypeReferenceBuilder(_fixture.ResourceResolver).ConvertTypeReference(CommonTypeRefComponent.Convert(typeRef)); } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs index b03b3079..0ee21ed2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs @@ -6,7 +6,6 @@ using FluentAssertions; using FluentAssertions.Equivalency; -using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Support; @@ -49,13 +48,13 @@ private async T.Task createSliceForElement(string canonical, str "Element does not match any slice and the group is closed."); private readonly SliceValidator.SliceCase _fixedSlice = new("Fixed", - new PathSelectorValidator("system", new FixedValidator(new FhirUri("http://example.com/some-bsn-uri").ToTypedElement())), + new PathSelectorValidator("system", new FixedValidator(new FhirUri("http://example.com/some-bsn-uri"))), new ElementSchema("#Patient.identifier:Fixed")); private static SliceValidator.SliceCase getPatternSlice(string profile) => new("PatternBinding", new PathSelectorValidator("system", new AllValidator(shortcircuitEvaluation: true, - new PatternValidator(new FhirUri("http://example.com/someuri").ToTypedElement()), + new PatternValidator(new FhirUri("http://example.com/someuri")), new BindingValidator("http://example.com/demobinding", strength: BindingValidator.BindingStrength.Required, context: $"{profile}#Patient.identifier.system"))), new ElementSchema("#Patient.identifier:PatternBinding")); @@ -222,11 +221,11 @@ public async T.Task TestResliceGeneration() #endif var expectedSlice = new SliceValidator(false, true, ResultAssertion.SUCCESS, new SliceValidator.SliceCase("phone", new PathSelectorValidator("system", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("phone").ToTypedElement()), + new FixedValidator(new Code("phone")), new BindingValidator(contactPointSystem, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.system"))), new ElementSchema("#Patient.telecom:phone")), new SliceValidator.SliceCase("email", new PathSelectorValidator("system", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("email").ToTypedElement()), + new FixedValidator(new Code("email")), new BindingValidator(contactPointSystem, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.system"))), new ElementSchema("#Patient.telecom:email")) ); @@ -244,11 +243,11 @@ void testResliceInSlice2(SliceValidator.SliceCase slice2) var email = new SliceValidator(false, false, _sliceClosedAssertion, new SliceValidator.SliceCase("email/home", new PathSelectorValidator("use", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("home").ToTypedElement()), + new FixedValidator(new Code("home")), new BindingValidator(contactPointUse, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.use"))), new ElementSchema("#Patient.telecom:email/home")), new SliceValidator.SliceCase("email/work", new PathSelectorValidator("use", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("work").ToTypedElement()), + new FixedValidator(new Code("work")), new BindingValidator(contactPointUse, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.use"))), new ElementSchema("#Patient.telecom:email/work")) ); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs new file mode 100644 index 00000000..94b54c77 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs @@ -0,0 +1,158 @@ +using FluentAssertions; +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation.Compilation.Tests +{ + [TestClass] + public class ScopedNodeOnDictionaryTests + { + + [TestMethod] + public void BaseToScopedNodeTests() + { + var humanName = new HumanName() { Family = "Brown", Given = new[] { "Joe" } }; + var fhirBool = new FhirBoolean(true); + fhirBool.AddExtension("http://example.org/extension", new FhirString("some extension")); + var patient = new Patient() { ActiveElement = fhirBool }; + patient.AddExtension("http://example.org/extension", new FhirString("some extension")); + patient.AddExtension("http://example.org/otherextions", new FhirDecimal(12)); + patient.Name.Add(new HumanName() { Family = "Doe", Given = ["John", "J."] }); + + + var node = printNode(patient.ToScopedNode()); + + node.Should().BeEquivalentTo(""" + { + Name: Patient + Value: + Type: Patient + } + { + Name: extension + Value: + Type: Extension + } + { + Name: url + Value: http://example.org/extension + Type: String + } + { + Name: value + Value: some extension + Type: string + } + { + Name: extension + Value: + Type: Extension + } + { + Name: url + Value: http://example.org/otherextions + Type: String + } + { + Name: value + Value: 12 + Type: decimal + } + { + Name: active + Value: True + Type: boolean + } + { + Name: extension + Value: + Type: Extension + } + { + Name: url + Value: http://example.org/extension + Type: String + } + { + Name: value + Value: some extension + Type: string + } + { + Name: name + Value: + Type: HumanName + } + { + Name: family + Value: Doe + Type: string + } + { + Name: given + Value: John + Type: string + } + { + Name: given + Value: J. + Type: string + } + + """); + + +#pragma warning disable CS0618 // Type or member is obsolete + string printNode(IBaseElementNavigator node, int depth = 0) where T : IBaseElementNavigator +#pragma warning restore CS0618 // Type or member is obsolete + { + var indent = new string(' ', depth * 2); + + var result = $$""" + {{indent}}{ + {{indent}} Name: {{node.Name}} + {{indent}} Value: {{node.Value}} + {{indent}} Type: {{node.InstanceType}} + {{indent}}} + + """; + foreach (var child in node.Children()) + result += printNode(child, depth + 1); + return result; + } + + + string printDictionary(IReadOnlyDictionary dict, int depth = 0) + { + var result = string.Empty; + var indent = new string(' ', depth * 2); + foreach (var node in dict) + { + result += $$""" + {{indent}}{ + {{indent}} Key: {{node.Key}} + {{indent}} Value: {{printValue(node.Value, depth)}} + {{indent}}} + + """; + } + return result; + } + + string printValue(object value, int depth) => + value switch + { + byte[] bt => string.Empty, + string or bool or decimal or DateTimeOffset or int or long or XHtml => value.ToString()!, + IReadOnlyDictionary dict => printDictionary(dict, depth + 1), + IEnumerable list => list.Select(l => printValue(l, depth)).Aggregate(string.Empty, (current, next) => current + next), + _ => throw new NotImplementedException() + }; + } + + } +} diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs index 3eb2237f..1660d89d 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs @@ -214,6 +214,26 @@ private static StructureDefinition buildSliceWithSecondaryTargetReferenceDiscrim (ElementDefinition.DiscriminatorType.Type, "content.ofType(Reference).resolve()")); cons.Add(slicingIntro); +#if R5 + // Intro slice child content[x] + cons.Add(new ElementDefinition("Communication.payload.content[x]") + .OrType(FHIRAllTypes.Attachment) + .OrReferenceWithProfiles( + new[] { "http://hl7.org/fhir/StructureDefinition/DocumentReference", + "http://hl7.org/fhir/StructureDefinition/Task" })); + + // Slice 1 ========================== + cons.Add(new ElementDefinition("Communication.payload") + { + ElementId = "Communication.payload:Attachment", + SliceName = "Attachment" + }); + + cons.Add(new ElementDefinition("Communication.payload.content[x]") + { + ElementId = "Communication.payload:Attachment.content[x]", + }.OfType(FHIRAllTypes.Attachment)); +#else // Intro slice child content[x] cons.Add(new ElementDefinition("Communication.payload.content[x]") .OrType(FHIRAllTypes.String) @@ -232,6 +252,7 @@ private static StructureDefinition buildSliceWithSecondaryTargetReferenceDiscrim { ElementId = "Communication.payload:String.content[x]", }.OfType(FHIRAllTypes.String)); +#endif // Slice 2 =========================== cons.Add(new ElementDefinition("Communication.payload") diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs new file mode 100644 index 00000000..e74ef506 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +using Firely.Fhir.Validation.Compilation.Tests; +using FluentAssertions; +using Hl7.Fhir.Model; +using Hl7.Fhir.Support; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +// Until we have Marco's IScopedNodeOnPoco adapter, I cannot write R5 tests using just the "shared" R4+ validator. +#if !R5 + +namespace Firely.Fhir.Validation.Tests +{ + [Trait("Category", "Validation")] + public class ValidatorHighLevelApiTests : IClassFixture + { + internal SchemaBuilderFixture _fixture; + + public ValidatorHighLevelApiTests(SchemaBuilderFixture fixture) + { + _fixture = fixture; + } + + private IEnumerable getErrorCodes(OperationOutcome oo) => oo.Issue.SelectMany(i => i.Details.Coding).Where(cd => cd.System == "http://hl7.org/fhir/dotnet-api-operation-outcome").Select(c => c.Code.ToString()); + + + [Fact] + public void ValidatesAndReportsError() + { + var p = new Patient() { Deceased = new FhirString("wrong") }; + var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); + var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); + + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code.ToString()); + result.Success.Should().BeFalse(); + + result = validator.Validate(p); + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code.ToString()); + result.Success.Should().BeFalse(); + } + + [Fact] + public void NoReferenceResolution() + { + // First, without a resolver, we do not follow references and thus do not validate the referenced resource. + var p = new Patient() { ManagingOrganization = new ResourceReference("http://example.com/orgA") }; + var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); + validator.Validate(p, Canonical.ForCoreType("Patient").ToString()).Success.Should().BeTrue(); + + // Now with reference resolution + var or = new InMemoryExternalReferenceResolver() { ["http://example.com/orgA"] = new Organization() }; + validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, or); + var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); + + // Organization should fail constraint org-1 + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code.ToString()); + } + + [Fact] + public void SkipConstraintValidation() + { + var o = new Organization(); + var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); + + // validate with constraint validation, it should fail on org-1 + var result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code.ToString()); + + // now, skip constraint validation + validator.SkipConstraintValidation = true; + result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); + result.Success.Should().BeTrue(); + } + } +} +#endif \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs index 69cd592e..c69efa55 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs @@ -28,7 +28,7 @@ public JToken ToJson() throw new System.NotImplementedException(); } - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { return new ResultReport(_result, new TraceAssertion(state.Location.InstanceLocation.ToString(), _message)); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs index b9c23bae..bc791450 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.ElementModel.Types; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -21,74 +21,73 @@ internal class FixedValidationData : BasicValidatorDataAttribute // integer yield return new object?[] { - new FixedValidator(10), - ElementNode.ForPrimitive(91), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [int]" + new FixedValidator(new Hl7.Fhir.Model.Integer(10)), + ElementNode.ForPrimitive(91), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [int]" }; yield return new object?[] { - new FixedValidator(90), - ElementNode.ForPrimitive(90), - true, null, "result must be true [int]" + new FixedValidator(new Hl7.Fhir.Model.Integer(90)), + ElementNode.ForPrimitive(90), + true, null, "result must be true [int]" }; // string yield return new object?[] { - new FixedValidator("test"), - ElementNode.ForPrimitive("testfailure"), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [string]" + new FixedValidator(new FhirString("test")), + ElementNode.ForPrimitive("testfailure"), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [string]" }; yield return new object?[] { - new FixedValidator("test"), - ElementNode.ForPrimitive("test"), - true, null,"result must be true [string]" + new FixedValidator(new FhirString("test")), + ElementNode.ForPrimitive("test"), + true, null,"result must be true [string]" }; // boolean yield return new object?[] { - new FixedValidator(true), - ElementNode.ForPrimitive(false), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [boolean]" + new FixedValidator(new FhirBoolean(true)), + ElementNode.ForPrimitive(false), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [boolean]" }; yield return new object?[] { - new FixedValidator(true), - ElementNode.ForPrimitive(true), - true, null, "result must be true [boolean]" + new FixedValidator(new FhirBoolean(true)), + ElementNode.ForPrimitive(true), + true, null, "result must be true [boolean]" }; // mixed primitive types yield return new object[] { - new FixedValidator(Date.Parse("2019-09-05")), - ElementNode.ForPrimitive(20190905), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [mixed]" + new FixedValidator(new Hl7.Fhir.Model.Date("2019-09-05")), + ElementNode.ForPrimitive(20190905), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [mixed]" }; // Complex Types yield return new object?[] { - new FixedValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), - ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } ), - true, null, "The input should match: family name should be Brown, and given name is Joe" + new FixedValidator(new HumanName() { Family = "Brown", Given = [ "Joe" ] } ), + ElementNodeAdapterExtensions.CreateHumanName("Brown", ["Joe"] ), + true, null, "The input should match: family name should be Brown, and given name is Joe" }; yield return new object?[] { - new FixedValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), - ElementNode.ForPrimitive("Brown, Joe Patrick"), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "String and HumanName are different" + new FixedValidator(new HumanName() { Family = "Brown", Given = [ "Joe" ] } ), + ElementNode.ForPrimitive("Brown, Joe Patrick"), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "String and HumanName are different" }; yield return new object?[] { - new FixedValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe", "Patrick" } )), - ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Patrick", "Joe" } ), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should not match the fixed" + new FixedValidator(new HumanName() { Family = "Brown", Given = [ "Joe" , "Patricks"] }), + ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Patrick", "Joe" } ), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should not match the fixed" }; yield return new object?[] { - new FixedValidator( - ElementNodeAdapterExtensions.CreateCoding("code", "system", systemFirstInOrder: false)), - ElementNodeAdapterExtensions.CreateCoding("code", "system", systemFirstInOrder: true), - true, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should match the fixed, also when the order is not the same" + new FixedValidator( new Coding("system", "code")), + ElementNodeAdapterExtensions.CreateCoding( "code", "system", false), + true, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should match the fixed, also when the order is not the same" }; } } @@ -100,7 +99,7 @@ public class FixedValidatorTests : BasicValidatorTests public void InvalidConstructors() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Action action = () => _ = new FixedValidator(null); + Action action = () => _ = new FixedValidator((DataType?)null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. action.Should().Throw(); } @@ -108,10 +107,10 @@ public void InvalidConstructors() [TestMethod] public void CorrectConstructor() { - var assertion = new FixedValidator(4); + var assertion = new FixedValidator(new Hl7.Fhir.Model.Integer(4)); assertion.Should().NotBeNull(); - assertion.FixedValue.Should().BeAssignableTo(); + assertion.FixedValue.Should().BeAssignableTo(); } [DataTestMethod] diff --git a/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs index d3cfd44a..1a3eac8b 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.ElementModel.Types; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -21,65 +21,65 @@ internal class PatternValidatorData : BasicValidatorDataAttribute // integer yield return new object?[] { - new PatternValidator(10), + new PatternValidator(new Hl7.Fhir.Model.Integer(10)), ElementNode.ForPrimitive(91), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [int]" }; yield return new object?[] { - new PatternValidator(90), + new PatternValidator(new Hl7.Fhir.Model.Integer(90)), ElementNode.ForPrimitive(90), true, null, "result must be true [int]" }; // string yield return new object?[] { - new PatternValidator("test"), + new PatternValidator(new FhirString("test")), ElementNode.ForPrimitive("testfailure"), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [string]" }; yield return new object?[] { - new PatternValidator("test"), + new PatternValidator(new FhirString("test")), ElementNode.ForPrimitive("test"), true, null,"result must be true [string]" }; // boolean yield return new object?[] { - new PatternValidator(true), + new PatternValidator(new FhirBoolean(true)), ElementNode.ForPrimitive(false), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [boolean]" }; yield return new object?[] { - new PatternValidator(true), + new PatternValidator(new FhirBoolean(true)), ElementNode.ForPrimitive(true), true, null, "result must be true [boolean]" }; // mixed primitive types yield return new object?[] { - new PatternValidator(Date.Parse("2019-09-05")), + new PatternValidator( new Hl7.Fhir.Model.Date("2019-09-05")), ElementNode.ForPrimitive(20190905), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [mixed]" }; // Complex types yield return new object?[] { - new PatternValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), + new PatternValidator(new HumanName() { Family = "Brown", Given = ["Joe" ]} ), ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe", "Patrick" } ), true, null, "The input should match the pattern: family name should be Brown, and given name is Joe" }; yield return new object?[] { - new PatternValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), + new PatternValidator(new HumanName() { Family = "Brown", Given = ["Joe" ]}), ElementNode.ForPrimitive("Brown, Joe Patrick"), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "String and HumanName are different" }; yield return new object?[] { - new PatternValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), + new PatternValidator(new HumanName() { Family = "Brown", Given = ["Joe" ]}), ElementNodeAdapterExtensions.CreateHumanName("Brown", Array.Empty() ), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "The input should not match the pattern" }; @@ -93,7 +93,7 @@ public class PatternValidatorTests : BasicValidatorTests public void InvalidConstructors() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Action action = () => _ = new PatternValidator(null); + Action action = () => _ = new PatternValidator((DataType?)null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. action.Should().Throw(); } @@ -101,10 +101,10 @@ public void InvalidConstructors() [TestMethod] public void CorrectConstructor() { - var assertion = new PatternValidator(4); + var assertion = new PatternValidator(new Hl7.Fhir.Model.Integer(4)); assertion.Should().NotBeNull(); - assertion.PatternValue.Should().BeAssignableTo(); + assertion.PatternValue.Should().BeAssignableTo(); } [DataTestMethod] diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs index de4b1d70..1a3a1df8 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs @@ -81,8 +81,9 @@ public static object createInstance(string reference) => [ReferencedInstanceValidatorTests] [DataTestMethod] - public void ValidateInstance(object instance, ReferencedInstanceValidator testee, bool success, string fragment) + public void ValidateInstance(object instance, object testeeo, bool success, string fragment) { + ReferencedInstanceValidator testee = (ReferencedInstanceValidator)testeeo; static ITypedElement? resolve(string url, string _) => url.StartsWith("http://example.com/hit") ? diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs index 89cd697f..00507dad 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using Hl7.Fhir.ElementModel; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; @@ -19,16 +20,16 @@ public void FollowMetaProfileTest() { profile = new[] { "profile1", "profile2", "profile3", "profile4" } } - }.ToTypedElement(); + }.ToTypedElement().AsScopedNode(); - var result = ResourceSchema.GetMetaProfileSchemas(instance, callback); + var result = ResourceSchema.GetMetaProfileSchemas(instance, callback, new ValidationState()); result.Should().BeEquivalentTo(new Canonical[] { "userprofile2", "profile3", "profile4", "userprofile5" }); - result = ResourceSchema.GetMetaProfileSchemas(instance, declineAll); + result = ResourceSchema.GetMetaProfileSchemas(instance, declineAll, new ValidationState()); result.Should().BeEmpty(); - // without a callback - result = ResourceSchema.GetMetaProfileSchemas(instance, null); + // without a callback: + result = ResourceSchema.GetMetaProfileSchemas(instance, null, new ValidationState()); result.Should().BeEquivalentTo(new Canonical[] { "profile1", "profile2", "profile3", "profile4" }); static Canonical[] callback(string location, Canonical[] orignalMetaProfiles) diff --git a/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs index 2e91088a..f6b76a84 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -18,7 +19,7 @@ public class SchemaReferenceValidatorTests public void InvokesCorrectSchema() { var schemaUri = "http://someotherschema"; - var schema = new ElementSchema(schemaUri, new ChildrenValidator(true, ("value", new FixedValidator("hi")))); + var schema = new ElementSchema(schemaUri, new ChildrenValidator(true, ("value", new FixedValidator(new FhirString("hi"))))); var resolver = new TestResolver() { schema }; var vc = ValidationContext.BuildMinimalContext(schemaResolver: resolver); @@ -46,7 +47,7 @@ public void ExtensionInvokesCorrectSchema() new StructureDefinitionInformation("http://hl7.org/fhir/StructureDefinition/Extension", null, "Extension", null, false)); var referredSchema = new ExtensionSchema( new StructureDefinitionInformation(schemaUri, null, "Extension", null, false), - new ChildrenValidator(true, ("value", new FixedValidator("hi")))); + new ChildrenValidator(true, ("value", new FixedValidator(new FhirString("hi"))))); var resolver = new TestResolver() { referredSchema }; var vc = ValidationContext.BuildMinimalContext(schemaResolver: resolver); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs index cfd589b3..42affea8 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; @@ -132,8 +133,8 @@ private static IEnumerable buildTestcase(params string[] instance private SliceValidator buildSliceAssertion(bool ordered, bool openAtEnd) => new(ordered, openAtEnd, DefaultEvidence, - new SliceValidator.SliceCase("slice1", new FixedValidator(ElementNode.ForPrimitive("slice1")), Slice1Evidence), - new SliceValidator.SliceCase("slice2", new FixedValidator(ElementNode.ForPrimitive("slice2")), Slice2Evidence)); + new SliceValidator.SliceCase("slice1", new FixedValidator(new FhirString("slice1")), Slice1Evidence), + new SliceValidator.SliceCase("slice2", new FixedValidator(new FhirString("slice2")), Slice2Evidence)); } } diff --git a/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs b/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs index 0028dd4b..513ee8b5 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; @@ -55,7 +56,7 @@ public void ValidateSchema() new SchemaReferenceValidator(stringSchema.Id), new CardinalityValidator(0, 1), new MaxLengthValidator(40), - new FixedValidator("Brown") + new FixedValidator(new FhirString("Brown")) ); var givenSchema = new ElementSchema("#given", @@ -143,13 +144,20 @@ static ITypedElement buildCodeableConcept(string system, string code) return result; } + static CodeableConcept buildCodeableConceptPoco(string system, string code) + { + var result = new CodeableConcept(); + result.Coding.Add(new Coding(system, code)); + return result; + } + var systolicSlice = new SliceValidator.SliceCase("systolic", - new PathSelectorValidator("code", new FixedValidator(buildCodeableConcept("http://loinc.org", "8480-6"))), + new PathSelectorValidator("code", new FixedValidator(buildCodeableConceptPoco("http://loinc.org", "8480-6"))), bpComponentSchema ); var dystolicSlice = new SliceValidator.SliceCase("dystolic", - new PathSelectorValidator("code", new FixedValidator(buildCodeableConcept("http://loinc.org", "8462-4"))), + new PathSelectorValidator("code", new FixedValidator(buildCodeableConceptPoco("http://loinc.org", "8462-4"))), bpComponentSchema ); diff --git a/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs b/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs index e4f1d766..6f083c08 100644 --- a/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs +++ b/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs @@ -68,7 +68,7 @@ internal static class ElementNodeAdapterExtensions public static ITypedElement CreateHumanName(string familyName, string[] givenNames) { var node = ElementNodeAdapter.Root("HumanName"); - if (string.IsNullOrEmpty(familyName)) + if (!string.IsNullOrEmpty(familyName)) node.Add("family", familyName, "string"); foreach (var givenName in givenNames) node.Add("given", givenName, "string"); diff --git a/test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs b/test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs new file mode 100644 index 00000000..c9a6b241 --- /dev/null +++ b/test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs @@ -0,0 +1,21 @@ +using Hl7.Fhir.ElementModel; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation.Tests +{ + /// + /// Helper class to make it easier to test and implementations with + /// the interface instead of . + /// + internal static class IValidatableExtensions + { + /// + public static ResultReport Validate(this IValidatable validatable, ITypedElement input, ValidationContext vc, ValidationState state) + => validatable.Validate(input.AsScopedNode(), vc, state); + + /// + public static ResultReport Validate(this IGroupValidatable validatable, IEnumerable input, ValidationContext vc, ValidationState state) + => validatable.Validate(input.Select(i => i.AsScopedNode()), vc, state); + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs b/test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs new file mode 100644 index 00000000..e05103ec --- /dev/null +++ b/test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs @@ -0,0 +1,61 @@ +using FluentAssertions; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Firely.Fhir.Validation.Tests.Support +{ + [TestClass] + public class ScopedNodeJsonTests + { + [TestMethod] + [DynamicData(nameof(AdditionData), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetTestDisplayNames))] + public void ToJTokenTest(DataType dataType, string expectedResult) + { + dataType.ToJToken().ToString().Should().Be(expectedResult); + } + + public static IEnumerable AdditionData() => GetDataTypeExamples().Select(x => new object[] { x.Item1, x.Item2 }); + + public static string GetTestDisplayNames(MethodInfo methodInfo, object[] values) => + $"{methodInfo.Name}({(values[0] is DataType dt ? dt.TypeName : values[0].GetType().Name)}, '{values[1]}')"; + + public static IEnumerable<(DataType, string)> GetDataTypeExamples() + { + yield return (new CodeableConcept() { Coding = [new("System", "code"), new("Sys2", "code2")] }, """ + { + "coding": [ + { + "system": "System", + "code": "code" + }, + { + "system": "Sys2", + "code": "code2" + } + ] + } + """); + yield return (new Coding("System", "Code"), """ + { + "system": "System", + "code": "Code" + } + """); + + yield return (new Money() { Currency = Money.Currencies.EUR, Value = 3 }, """ + { + "value": 3.0, + "currency": "EUR" + } + """); + yield return (new FhirBoolean(true), "True"); + yield return (new Code("male"), "male"); + yield return (new FhirDateTime(2023, 12, 05), "2023-12-05"); + yield return (new FhirDecimal(5), "5"); + yield return (new FhirString("a string"), "a string"); + } + } +} diff --git a/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs b/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs index 725939a4..12f22e37 100644 --- a/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs +++ b/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs @@ -54,9 +54,13 @@ public TypedElementOnDictionary(string rootName, IDictionary wra public string Name => _name; +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public string? InstanceType => TryGetValue("_type", out var type) ? type as string : ResourceType; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public object? Value => TryGetValue("_value", out var value) ? value : null; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public string Location => _location; @@ -92,7 +96,7 @@ private record ConstantElement(string Name, string InstanceType, object Value, s { public IElementDefinitionSummary? Definition => null; - public IEnumerable Children(string name) => + public IEnumerable Children(string? name) => Enumerable.Empty(); } diff --git a/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj b/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj index 1704f62d..778077cb 100644 --- a/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj +++ b/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj @@ -12,10 +12,6 @@ - - - - PreserveNewest