From 3f7ee8978441078680ff08058045a604e0e4535d Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 14 Aug 2023 15:04:13 +0200 Subject: [PATCH 1/3] feature: Added new txt-1 and txt-2 validation components --- .../SchemaBuilders/FhirPathBuilder.cs | 2 + .../Impl/FhirTxt1Validator.cs | 57 +++++++++++++++++++ .../Impl/FhirTxt2Validator.cs | 42 ++++++++++++++ .../SchemaBuilders/FhirPathBuilderTests.cs | 14 ++++- .../SimpleValidationsTests.cs | 25 ++++++++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs create mode 100644 src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs index c9fd9c3e..157964c9 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs @@ -50,6 +50,8 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv { "ele-1" => new FhirEle1Validator(), "ext-1" => new FhirExt1Validator(), + "txt-1" => new FhirTxt1Validator(), + "txt-2" => new FhirTxt2Validator(), _ => null }; } diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs new file mode 100644 index 00000000..713ae8a4 --- /dev/null +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -0,0 +1,57 @@ +/* + * 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 Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Hl7.Fhir.Support; +using Newtonsoft.Json.Linq; +using System.Linq; +using System.Runtime.Serialization; + +namespace Firely.Fhir.Validation +{ + /// + /// Represents the hand-coded version of the equivalent running invariant "ext-1". + /// + [DataContract] + public class FhirTxt1Validator : InvariantValidator + { + /// + public override string Key => "txt-1"; + + /// + public override OperationOutcome.IssueSeverity? Severity => OperationOutcome.IssueSeverity.Error; + + /// + public override bool BestPractice => false; + + /// + 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 _) + { + // Original expression: "expression": "htmlChecks()" + + if (input.Value is null) return (false, null); + + var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString(), out var errors); + + if (result) + { + return (true, null); + } + else + { + var issues = errors.Select(e => new IssueAssertion(Issue.XSD_VALIDATION_ERROR, e)); + return (false, new ResultReport(ValidationResult.Failure, issues)); + } + } + + /// + public override JToken ToJson() => new JProperty("FastInvariant-txt1", new JObject()); + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs new file mode 100644 index 00000000..47ccd9aa --- /dev/null +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs @@ -0,0 +1,42 @@ +/* + * 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 Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Newtonsoft.Json.Linq; +using System.Runtime.Serialization; + +namespace Firely.Fhir.Validation +{ + /// + /// Represents the hand-coded version of the equivalent running invariant "ext-1". + /// + [DataContract] + public class FhirTxt2Validator : InvariantValidator + { + /// + public override string Key => "txt-2"; + + /// + public override OperationOutcome.IssueSeverity? Severity => OperationOutcome.IssueSeverity.Error; + + /// + public override bool BestPractice => false; + + /// + public override string? HumanDescription => "The narrative SHALL have some non-whitespace content"; + + /// + protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + { + //Check whether the narrative contains non-whitespace content. + return (!string.IsNullOrWhiteSpace(input.Value.ToString()), null); + } + + /// + public override JToken ToJson() => new JProperty("FastInvariant-txt2", new JObject()); + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs index a0da27df..5d2c434c 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs @@ -53,7 +53,15 @@ public void HandcodedFPConstraintsTest() new() { Key = "ext-1" - } + }, + new() + { + Key = "txt-1" + }, + new() + { + Key = "txt-2" + }, } }; @@ -62,11 +70,13 @@ public void HandcodedFPConstraintsTest() var result = _sut.Build(nav, ElementConversionMode.Full); var builders = result.Should() - .HaveCount(2).And + .HaveCount(4).And .AllBeAssignableTo().Which; builders.First().Should().BeOfType(); builders.Skip(1).First().Should().BeOfType(); + builders.Skip(2).First().Should().BeOfType(); + builders.Skip(3).First().Should().BeOfType(); } [TestMethod] diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs index 79304178..9bc3ef6a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs @@ -166,6 +166,31 @@ public void ValidateExtensionCardinality() results = patientSchema!.Validate(patient.ToTypedElement(), _fixture.NewValidationContext()); results.IsSuccessful.Should().Be(true, because: "extensions have the correct cardinality"); } + + [Fact] + public void ValidateNarrativeInvariants() + { + var justWhiteSpace = new Narrative + { + Status = Narrative.NarrativeStatus.Additional, + Div = " " + }; + + var invalidHtml = new Narrative + { + Status = Narrative.NarrativeStatus.Additional, + Div = "

" + }; + + var narrativeSchema = _fixture.SchemaResolver.GetSchema("http://hl7.org/fhir/StructureDefinition/Narrative"); + + var results = narrativeSchema!.Validate(justWhiteSpace.ToTypedElement(), _fixture.NewValidationContext()); + results.IsSuccessful.Should().Be(false, "Instance failed constraint txt-2 \"The narrative SHALL have some non-whitespace content\""); + + results = narrativeSchema!.Validate(invalidHtml.ToTypedElement(), _fixture.NewValidationContext()); + results.IsSuccessful.Should().Be(false, "Invalid Xml encountered. Details: The 'p' start tag on line 1 position 7 does not match the end tag of 'div'. Line 1, position 11."); + + } } } From d2de4535602eb177acae1ae133e97fa68e87c5ea Mon Sep 17 00:00:00 2001 From: mmsmits Date: Wed, 16 Aug 2023 13:06:21 +0200 Subject: [PATCH 2/3] fix unit tests --- .../FhirTestCases | 2 +- .../SimpleValidationsTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index a0aba985..35f2a5c9 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit a0aba985582d6ec94e409e0772ea097bda26a4f7 +Subproject commit 35f2a5c9fb830acd6030d8721dbee9255c7928e9 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs index 9bc3ef6a..babea9de 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs @@ -179,7 +179,7 @@ public void ValidateNarrativeInvariants() var invalidHtml = new Narrative { Status = Narrative.NarrativeStatus.Additional, - Div = "

" + Div = "
" }; var narrativeSchema = _fixture.SchemaResolver.GetSchema("http://hl7.org/fhir/StructureDefinition/Narrative"); @@ -188,7 +188,7 @@ public void ValidateNarrativeInvariants() results.IsSuccessful.Should().Be(false, "Instance failed constraint txt-2 \"The narrative SHALL have some non-whitespace content\""); results = narrativeSchema!.Validate(invalidHtml.ToTypedElement(), _fixture.NewValidationContext()); - results.IsSuccessful.Should().Be(false, "Invalid Xml encountered. Details: The 'p' start tag on line 1 position 7 does not match the end tag of 'div'. Line 1, position 11."); + results.IsSuccessful.Should().Be(false, "The element 'div' in namespace 'http://www.w3.org/1999/xhtml' has invalid child element 'script' in namespace 'http://www.w3.org/1999/xhtml'. List of possible elements expected: 'p, h1, h2, h3, h4, h5, h6, div, ul, ol, dl, pre, hr, blockquote, address, table, a, br, span, bdo, map, img, tt, i, b, big, small, em, strong, dfn, code, q, samp, kbd, var, cite, abbr, acronym, sub, sup' in namespace 'http://www.w3.org/1999/xhtml'."); } } From 8a31e231de44d07ca3cfde2c5eb2e9f25f55c342 Mon Sep 17 00:00:00 2001 From: mmsmits Date: Wed, 16 Aug 2023 13:07:00 +0200 Subject: [PATCH 3/3] throw error when narrative is not of type string --- .../Impl/FhirTxt1Validator.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs index 713ae8a4..5cc89663 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -38,17 +38,28 @@ protected override (bool, ResultReport?) RunInvariant(ITypedElement input, Valid if (input.Value is null) return (false, null); - var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString(), out var errors); - - if (result) - { - return (true, null); - } - else + switch (input.Value) { - var issues = errors.Select(e => new IssueAssertion(Issue.XSD_VALIDATION_ERROR, e)); - return (false, new ResultReport(ValidationResult.Failure, issues)); - } + case string value: + { + var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString(), out var errors); + + if (result) + { + return (true, null); + } + else + { + var issues = errors.Select(e => new IssueAssertion(Issue.XSD_VALIDATION_ERROR, e)); + return (false, new ResultReport(ValidationResult.Failure, issues)); + } + } + default: + 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()})"))); + + } } ///