From 26784ba7c8928a35df0e4e34d6d9e0631a7d45a7 Mon Sep 17 00:00:00 2001 From: Etienne Poirier <33007955+epeartree@users.noreply.github.com> Date: Thu, 31 Mar 2022 14:47:15 -0400 Subject: [PATCH] Fixing ValueSet expansion not supporting canonical url. (#3501) * 2991 - WIP, starting to implement test to reproduce the issue. * Adding capability to extract url & version from the valueSet.include.url * parsing codeSystem canonical URL to extra version if required. * WIP * Cleaning up the test. * Replacing hard code value with variable. * Removing non required test. * Modifications following first code review. * Further modifications following first code review. * Refining the test for better code coverage. * Prefixing the change log file name with the hapi-fhir issue number. Co-authored-by: Etienne Poirier --- ...-system-uri-during-valueset-expansion.yaml | 5 +++ ...oryTerminologyServerValidationSupport.java | 30 ++++++++++++--- ...erminologyServerValidationSupportTest.java | 38 ++++++++++++++++++- 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_0_0/3515-supporting-canonical-system-uri-during-valueset-expansion.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_0_0/3515-supporting-canonical-system-uri-during-valueset-expansion.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_0_0/3515-supporting-canonical-system-uri-during-valueset-expansion.yaml new file mode 100644 index 00000000000..41a6575b391 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_0_0/3515-supporting-canonical-system-uri-during-valueset-expansion.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 3515 +jira: SMILE-2991 +title: "Supporting expansion of ValueSet include/exclude system URI expressed in canonical format during validation." diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index 3a1d6817baf..e56eda7b765 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -1,12 +1,12 @@ package org.hl7.fhir.common.hapi.validation.support; -import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.FhirVersionIndependentConcept; import org.apache.commons.lang3.Validate; @@ -36,9 +36,12 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import static org.apache.commons.lang3.StringUtils.contains; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.substringAfter; +import static org.apache.commons.lang3.StringUtils.substringBefore; /** * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes @@ -47,6 +50,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * external term service API) */ public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { + private static final String OUR_PIPE_CHARACTER = "|"; + private final FhirContext myCtx; public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { @@ -539,13 +544,15 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } /** - * Returns true if at least one code was addded + * Returns true if at least one code was added */ private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException { + String wantSystemUrl = null; String wantSystemVersion = null; + if (theWantSystemUrlAndVersion != null) { - int versionIndex = theWantSystemUrlAndVersion.indexOf("|"); + int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); if (versionIndex > -1) { wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); @@ -554,15 +561,20 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } } + String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); + String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); + Function codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); Function valueSetLoader = newValueSetLoader(theValidationSupportContext); List nextCodeList = new ArrayList<>(); - String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); - String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); CodeSystem includeOrExcludeSystemResource = null; + if (isNotBlank(includeOrExcludeConceptSystemUrl)) { + includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); + includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); + if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { return false; } @@ -573,7 +585,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu String loadedCodeSystemUrl; if (includeOrExcludeConceptSystemVersion != null) { - loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion; + loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; } else { loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; } @@ -810,6 +822,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } } + private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { + if(contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)){ + theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); + } + return theVersion; + } public enum FailureType { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java index bd904241639..2953280fbf0 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java @@ -14,6 +14,8 @@ import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.ValueSet; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -423,8 +425,42 @@ public class InMemoryTerminologyServerValidationSupportTest { assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay()); } + @ParameterizedTest + @ValueSource(strings = {"http://terminology.hl7.org/CodeSystem/v2-0360|2.7","http://terminology.hl7.org/CodeSystem/v2-0360"}) + void testValidateCodeInValueSet_VsExpandedWithIncludes(String theCodeSystemUri) { + ConceptValidationOptions options = new ConceptValidationOptions(); + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + String codeMD = "MD"; - private static class PrePopulatedValidationSupportDstu2 extends PrePopulatedValidationSupport { + CodeSystem cs = new CodeSystem(); + cs.setStatus(Enumerations.PublicationStatus.ACTIVE); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + cs.setUrl(theCodeSystemUri); + cs.addConcept() + .setCode(codeMD) + .setDisplay("Doctor of Medicine"); + myPrePopulated.addCodeSystem(cs); + + ValueSet theValueSet = new ValueSet(); + theValueSet.setUrl("http://someValueSetURL"); + theValueSet.setVersion("0360"); + theValueSet.getCompose().addInclude().setSystem(theCodeSystemUri); + + String theCodeToValidateCodeSystemUrl = theCodeSystemUri; + String theCodeToValidate = codeMD; + + IValidationSupport.CodeValidationResult codeValidationResult = mySvc.validateCodeInValueSet( + valCtx, + options, + theCodeToValidateCodeSystemUrl, + theCodeToValidate, + null, + theValueSet); + + assertTrue(codeValidationResult.isOk()); + } + + private static class PrePopulatedValidationSupportDstu2 extends PrePopulatedValidationSupport { private final Map myDstu2ValueSets; PrePopulatedValidationSupportDstu2(FhirContext theFhirContext) {