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 <etienne.poirier@smilecdr.com>
This commit is contained in:
Etienne Poirier 2022-03-31 14:47:15 -04:00 committed by GitHub
parent 9e0c364fb4
commit 26784ba7c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 7 deletions

View File

@ -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."

View File

@ -1,12 +1,12 @@
package org.hl7.fhir.common.hapi.validation.support; 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.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.util.FhirVersionIndependentConcept; import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -36,9 +36,12 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; 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.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; 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 * 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) * external term service API)
*/ */
public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { public class InMemoryTerminologyServerValidationSupport implements IValidationSupport {
private static final String OUR_PIPE_CHARACTER = "|";
private final FhirContext myCtx; private final FhirContext myCtx;
public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) {
@ -539,13 +544,15 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
/** /**
* Returns <code>true</code> if at least one code was addded * Returns <code>true</code> if at least one code was added
*/ */
private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException { private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException {
String wantSystemUrl = null; String wantSystemUrl = null;
String wantSystemVersion = null; String wantSystemVersion = null;
if (theWantSystemUrlAndVersion != null) { if (theWantSystemUrlAndVersion != null) {
int versionIndex = theWantSystemUrlAndVersion.indexOf("|"); int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER);
if (versionIndex > -1) { if (versionIndex > -1) {
wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex);
wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1);
@ -554,15 +561,20 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
} }
String includeOrExcludeConceptSystemUrl = theInclude.getSystem();
String includeOrExcludeConceptSystemVersion = theInclude.getVersion();
Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext);
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = newValueSetLoader(theValidationSupportContext); Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = newValueSetLoader(theValidationSupportContext);
List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>(); List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>();
String includeOrExcludeConceptSystemUrl = theInclude.getSystem();
String includeOrExcludeConceptSystemVersion = theInclude.getVersion();
CodeSystem includeOrExcludeSystemResource = null; CodeSystem includeOrExcludeSystemResource = null;
if (isNotBlank(includeOrExcludeConceptSystemUrl)) { if (isNotBlank(includeOrExcludeConceptSystemUrl)) {
includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion);
includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER);
if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) {
return false; return false;
} }
@ -573,7 +585,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
String loadedCodeSystemUrl; String loadedCodeSystemUrl;
if (includeOrExcludeConceptSystemVersion != null) { if (includeOrExcludeConceptSystemVersion != null) {
loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion; loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion;
} else { } else {
loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; 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 { public enum FailureType {

View File

@ -14,6 +14,8 @@ import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -423,6 +425,40 @@ public class InMemoryTerminologyServerValidationSupportTest {
assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay()); 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";
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 static class PrePopulatedValidationSupportDstu2 extends PrePopulatedValidationSupport {
private final Map<String, IBaseResource> myDstu2ValueSets; private final Map<String, IBaseResource> myDstu2ValueSets;