diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs index fb1f4f33..d58c9403 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs @@ -510,10 +510,9 @@ internal IAssertion CreateSliceValidator(ElementDefinitionNavigator root) convertElementToSchema(schemaId + ":" + "condition", root) : buildDiscriminatorCondition(slicing, root); - // Check for always true/false cases. - if (condition is IFixedResult ra) - throw new IncorrectElementDefinitionException($"Encountered an ElementDefinition {root.Current.ElementId} that always" + - $"results in {ra.FixedResult} for its discriminator(s) and therefore cannot be used as a slicing discriminator."); + // A condition that always succeeds (or always fails) makes the slice superfluous (it matches + // everything, or nothing), but that is not wrong, so it should not stop the schema from being + // built. The SliceValidator handles such a condition correctly as it is. // If this is a normal slice, the constraints for the case to run are the constraints under this node. // In the case of a discriminator-less match, the case condition itself was a full validation of all diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SuperfluousSliceTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SuperfluousSliceTests.cs new file mode 100644 index 00000000..6ad5ed69 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SuperfluousSliceTests.cs @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://github.com/FirelyTeam/firely-validator-api/blob/main/LICENSE + */ + +using FluentAssertions; +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Source; +using Hl7.Fhir.Specification.Terminology; +using System.Linq; +using Xunit; + +namespace Firely.Fhir.Validation.Tests +{ + public class SuperfluousSliceTests + { + private const string PROFILE_URL = "http://example.org/fhir/StructureDefinition/SuperfluousSliceTestProfile"; + + private static StructureDefinition profile(string type, params ElementDefinition[] differential) => new() + { + Url = PROFILE_URL, + Name = "SuperfluousSliceTestProfile", + Status = PublicationStatus.Draft, + FhirVersion = FHIRVersion.N4_0_1, + Kind = StructureDefinition.StructureDefinitionKind.Resource, + Abstract = false, + Type = type, + BaseDefinition = $"http://hl7.org/fhir/StructureDefinition/{type}", + Derivation = StructureDefinition.TypeDerivationRule.Constraint, + Differential = new StructureDefinition.DifferentialComponent { Element = differential.ToList() } + }; + + private static OperationOutcome validate(StructureDefinition sd, Resource instance) + { + var source = new SnapshotSource(new MultiResolver(new InMemoryProfileResolver(sd), ZipSource.CreateValidationSource())); + var validator = new Validator(source.AsAsync(), new LocalTerminologyService(source.AsAsync())); + return validator.Validate(instance, PROFILE_URL); + } + + /// + /// A slice whose discriminator always succeeds (here: a 'profile' discriminator on an + /// element without any profiles) is superfluous, but not wrong. See issue #662. + /// + [Fact] + public void SuperfluousSliceShouldNotFailSchemaCompilation() + { + var sd = profile("Patient", + new ElementDefinition("Patient.name") + { + ElementId = "Patient.name", + Slicing = new ElementDefinition.SlicingComponent + { + Rules = ElementDefinition.SlicingRules.Open, + Discriminator = + [ + new ElementDefinition.DiscriminatorComponent + { + Type = ElementDefinition.DiscriminatorType.Profile, Path = "$this" + } + ] + } + }, + new ElementDefinition("Patient.name") + { + ElementId = "Patient.name:only", + SliceName = "only", + Min = 0, + Max = "*" + }); + + var patient = new Patient { Name = [new HumanName { Family = "Kramer" }] }; + + var outcome = validate(sd, patient); + + outcome.Success.Should().BeTrue(outcome.ToString()); + } + } +}