diff --git a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs index c8fcb7d9..958db772 100644 --- a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs @@ -73,23 +73,38 @@ public MinMaxValueValidator(ITypedElement limit, ValidationMode minMaxType) Limit = limit ?? throw new ArgumentNullException(nameof(limit), $"{nameof(limit)} cannot be null"); MinMaxType = minMaxType; - if (Any.TryConvert(Limit.Value, out _minMaxAnyValue!) == false) - throw new IncorrectElementDefinitionException($"Cannot convert the limit value ({Limit.Value}) to a comparable primitive."); + if (limit.InstanceType == "Quantity") //Quantity is the only non primitive that can be used as min/max value; + { + + var quantity = limit.ParseQuantity()?.ToQuantity()!; // first parse to a Hl7.Model Qunatity, which we convert to a Hl7.Fhir.ElementModel.Types Quantity + if (quantity is not null) + { + _minMaxAnyValue = quantity!; + } + + else + throw new IncorrectElementDefinitionException($"Cannot convert the limit value ({limit.Value}) to a quantity for comparison."); + } + else + { + if (Any.TryConvert(Limit.Value, out _minMaxAnyValue!) == false) + throw new IncorrectElementDefinitionException($"Cannot convert the limit value ({Limit.Value}) to a comparable primitive."); + + // Min/max are only defined for ordered types + if (!isOrderedType(_minMaxAnyValue)) + throw new IncorrectElementDefinitionException($"{Limit.Name} was given in ElementDefinition, but type '{Limit.InstanceType}' is not an ordered type"); + + static bool isOrderedType(Any value) => value is ICqlOrderable; + } _comparisonOutcome = MinMaxType == ValidationMode.MinValue ? -1 : 1; _comparisonLabel = _comparisonOutcome == -1 ? "smaller than" : _comparisonOutcome == 0 ? "equal to" : "larger than"; _comparisonIssue = _comparisonOutcome == -1 ? Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_TOO_SMALL : - Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_TOO_LARGE; - + Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_TOO_LARGE; _minMaxLabel = $"{MinMaxType.GetLiteral().Uncapitalize()}"; - // Min/max are only defined for ordered types - if (!isOrderedType(_minMaxAnyValue)) - throw new IncorrectElementDefinitionException($"{Limit.Name} was given in ElementDefinition, but type '{Limit.InstanceType}' is not an ordered type"); - - static bool isOrderedType(Any value) => value is ICqlOrderable; } /// @@ -98,10 +113,24 @@ public MinMaxValueValidator(long limit, ValidationMode minMaxType) : this(Elemen /// ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings _, ValidationState s) { - if (!Any.TryConvert(input.Value, out var instanceValue)) + Any instanceValue; + if (input.InstanceType == "Quantity") + { + var quantity = input.ParseQuantity()?.ToQuantity()!; // first parse to a Hl7.Model Qunatity, which we convert to a Hl7.Fhir.ElementModel.Types Quantity + if (quantity is not null) + { + instanceValue = quantity; + } + else + { + return new IssueAssertion(Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_NOT_COMPARABLE, + $"Value '{input.Value ?? input}' cannot be compared with {_minMaxAnyValue})").AsResult(s); + } + } + else if (!Any.TryConvert(input.Value, out instanceValue!)) { return new IssueAssertion(Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_NOT_COMPARABLE, - $"Value '{input.Value}' cannot be compared with {Limit.Value})").AsResult(s); + $"Value '{input.Value}' cannot be compared with {_minMaxAnyValue})").AsResult(s); } try @@ -113,17 +142,17 @@ ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings _, Vali (false, true) => 1, _ => 0 }; - + if (intResult == _comparisonOutcome) { - return new IssueAssertion(_comparisonIssue, $"Value '{input.Value}' is {_comparisonLabel} {Limit.Value})") + return new IssueAssertion(_comparisonIssue, $"Value '{instanceValue}' is {_comparisonLabel} {_minMaxAnyValue})") .AsResult(s); } } catch (ArgumentException) { return new IssueAssertion(Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_NOT_COMPARABLE, - $"Value '{input.Value}' cannot be compared with {Limit.Value})") + $"Value '{instanceValue}' cannot be compared with {_minMaxAnyValue})") .AsResult(s); } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 8c5a7331..8068ff26 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 8c5a7331020a580da9e2a0b19c941221c390e4d7 +Subproject commit 8068ff2636300fe1299af6c62c47247839688786 diff --git a/test/Firely.Fhir.Validation.Tests/Impl/MinMaxValueValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/MinMaxValueValidatorTests.cs index 52dfada7..ad52f7af 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/MinMaxValueValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/MinMaxValueValidatorTests.cs @@ -8,7 +8,6 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.ElementModel.Types; using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -116,6 +115,42 @@ public void InvalidConstructors() action.Should().Throw(); } + [TestMethod] + public void QuantityComparison() + { + var maxValue = new Hl7.Fhir.Model.Quantity + { + Value = 4, + Unit = "kg", + System = "http://unitsofmeasure.org", + Code = "kg" + }.ToTypedElement(ModelInfo.ModelInspector); + + var assertion = new MinMaxValueValidator(maxValue, MinMaxValueValidator.ValidationMode.MaxValue); + + assertion.Should().NotBeNull(); + assertion.Limit.Should().BeAssignableTo(); + + var quantityCorrect = new Quantity + { + Value = 3, + Unit = "kg", + System = "http://unitsofmeasure.org", + Code = "kg" + }.ToTypedElement(ModelInfo.ModelInspector); + + base.BasicValidatorTestcases(assertion, quantityCorrect, true, null, ""); + + var quantityIncorrect = new Quantity + { + Value = 5, + Unit = "kg", + System = "http://unitsofmeasure.org", + Code = "kg" + }.ToTypedElement(ModelInfo.ModelInspector); + base.BasicValidatorTestcases(assertion, quantityIncorrect, false, Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_TOO_LARGE, ""); + } + [TestMethod] public void CorrectConstructor() {