Skip to content

Commit

Permalink
Fixes for the translation of parameter issues as part of the output f…
Browse files Browse the repository at this point in the history
…or $validate-code operation. (#6438)

* Move the validation providers to the test utilities package such that they can be reused.

* Update IValidationSupport.CodeValidationIssue structure such that it meets the FHIR specification. Update RemoteTerminologyServiceValidationSupport and VersionSpecificWorkerContextWrapper translation for issues.

* New tests for issue translation. Move test class to a different package so that we can add another test class.

* Some simplification in the issue API to simplify building of issues.

* Fix compilation errors.

* Update providers to allow multiple responses and add  support for the fetchCodeSystem call through method find.

* Setup the first test for resource validation with remote terminology providers.

* Fix NullPointerException

* avoid calling validateCode for CodeSystem where system is null

* Keep old public API methods (and class name) in IValidationSupport and mark them as deprecated to avoid breaking dependencies.

* Revert local change to debug for duplicate errors.

* Add more test cases for resource validation. Throw exception to signal missing test setup to make it obvious.

* Simplify test setup.

* Add some more javadoc

* Add javadoc for the new test class

* Add more tests

* Address code review comments in IValidationSupport.

* Add changelog
  • Loading branch information
codeforgreen authored Nov 5, 2024
1 parent 34359b2 commit 3f042fd
Show file tree
Hide file tree
Showing 56 changed files with 2,300 additions and 775 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
type: fix
issue: 6422
title: "Previously, since 7.4.4 the validation issue detail codes were not translated correctly for Remote Terminology
validateCode calls. The detail code used was `invalid-code` for all use-cases which resulted in profile binding strength
not being applied to the issue severity as expected when validating resources against a profile.
This has been fixed and issue detail codes are translated correctly."
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,8 @@ private void expandValueSetHandleIncludeOrExclude(
if (theExpansionOptions != null
&& !theExpansionOptions.isFailOnMissingCodeSystem()
// Code system is unknown, therefore NOT_FOUND
&& e.getCodeValidationIssue().getCoding() == CodeValidationIssueCoding.NOT_FOUND) {
&& e.getCodeValidationIssue()
.hasIssueDetailCode(CodeValidationIssueCoding.NOT_FOUND.getCode())) {
return;
}
throw new InternalErrorException(Msg.code(888) + e);
Expand Down Expand Up @@ -2190,7 +2191,7 @@ private CodeValidationResult createFailureCodeValidationResult(
.setSeverity(IssueSeverity.ERROR)
.setCodeSystemVersion(theCodeSystemVersion)
.setMessage(theMessage)
.addCodeValidationIssue(new CodeValidationIssue(
.addIssue(new CodeValidationIssue(
theMessage,
IssueSeverity.ERROR,
CodeValidationIssueCode.CODE_INVALID,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
package ca.uhn.fhir.jpa.provider.r4;
package ca.uhn.fhir.jpa.validation;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import jakarta.servlet.http.HttpServletRequest;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -33,25 +25,23 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.util.ArrayList;
import java.util.List;

import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/*
/**
* This set of integration tests that instantiates and injects an instance of
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport}
* into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology
* implementation. It also exercises the code found in
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode}
* implementation. It also exercises the validateCode output translation code found in
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport}
*/
public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeOperationWithRemoteTerminologyR4Test.class);
public class ValidateCodeWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeWithRemoteTerminologyR4Test.class);
private static final String DISPLAY = "DISPLAY";
private static final String DISPLAY_BODY_MASS_INDEX = "Body mass index (BMI) [Ratio]";
private static final String CODE_BODY_MASS_INDEX = "39156-5";
Expand All @@ -64,8 +54,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);

private RemoteTerminologyServiceValidationSupport mySvc;
private MyCodeSystemProvider myCodeSystemProvider;
private MyValueSetProvider myValueSetProvider;
private IValidationProviders.MyValidationProvider<CodeSystem> myCodeSystemProvider;
private IValidationProviders.MyValidationProvider<ValueSet> myValueSetProvider;

@Autowired
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
Expand All @@ -76,8 +66,8 @@ public void before() throws Exception {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
myValidationSupportChain.addValidationSupport(0, mySvc);
myCodeSystemProvider = new MyCodeSystemProvider();
myValueSetProvider = new MyValueSetProvider();
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4();
ourRestfulServerExtension.registerProvider(myCodeSystemProvider);
ourRestfulServerExtension.registerProvider(myValueSetProvider);
}
Expand All @@ -103,20 +93,20 @@ public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDiffere

@Test
public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() {
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247"));
myCodeSystemProvider.myReturnParams = new Parameters();
myCodeSystemProvider.myReturnParams.addParameter("result", true);
myCodeSystemProvider.myReturnParams.addParameter("display", DISPLAY);
final String code = "P";
final String system = CODE_SYSTEM_V2_0247_URI;;

Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY);
setupCodeSystemValidateCode(system, code, params);

logAllConcepts();

Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "coding", new Coding().setSystem(CODE_SYSTEM_V2_0247_URI).setCode("P"))
.andParameter("url", new UriType(CODE_SYSTEM_V2_0247_URI))
.withParameter(Parameters.class, "coding", new Coding().setSystem(system).setCode(code))
.andParameter("url", new UriType(system))
.execute();

String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
Expand All @@ -128,7 +118,7 @@ public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSys

@Test
public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() {
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.setShouldThrowExceptionForResourceNotFound(false);

Parameters respParam = myClient
.operation()
Expand Down Expand Up @@ -166,21 +156,21 @@ public void validateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent

@Test
public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() {
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
myValueSetProvider.myReturnValueSets = new ArrayList<>();
myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
myValueSetProvider.myReturnParams = new Parameters();
myValueSetProvider.myReturnParams.addParameter("result", true);
myValueSetProvider.myReturnParams.addParameter("display", DISPLAY);
final String code = "alerts";
final String system = "http://terminology.hl7.org/CodeSystem/list-example-use-codes";
final String valueSetUrl = "http://hl7.org/fhir/ValueSet/list-example-codes";

Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY);
setupValueSetValidateCode(valueSetUrl, system, code, params);
setupCodeSystemValidateCode(system, code, params);

Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "code", new CodeType("alerts"))
.andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/list-example-use-codes"))
.andParameter("url", new UriType("http://hl7.org/fhir/ValueSet/list-example-codes"))
.withParameter(Parameters.class, "code", new CodeType(code))
.andParameter("system", new UriType(system))
.andParameter("url", new UriType(valueSetUrl))
.useHttpGet()
.execute();

Expand All @@ -193,21 +183,20 @@ public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSyste

@Test
public void validateCodeOperationOnValueSet_byUrlSystemAndCode() {
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
myValueSetProvider.myReturnValueSets = new ArrayList<>();
myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
myValueSetProvider.myReturnParams = new Parameters();
myValueSetProvider.myReturnParams.addParameter("result", true);
myValueSetProvider.myReturnParams.addParameter("display", DISPLAY_BODY_MASS_INDEX);
final String code = CODE_BODY_MASS_INDEX;
final String system = "http://terminology.hl7.org/CodeSystem/list-example-use-codes";
final String valueSetUrl = "http://hl7.org/fhir/ValueSet/list-example-codes";

Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY_BODY_MASS_INDEX);
setupValueSetValidateCode(valueSetUrl, system, code, params);

Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "code", new CodeType(CODE_BODY_MASS_INDEX))
.andParameter("url", new UriType("https://loinc.org"))
.andParameter("system", new UriType("http://loinc.org"))
.withParameter(Parameters.class, "code", new CodeType(code))
.andParameter("url", new UriType(valueSetUrl))
.andParameter("system", new UriType(system))
.execute();

String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
Expand All @@ -219,7 +208,7 @@ public void validateCodeOperationOnValueSet_byUrlSystemAndCode() {

@Test
public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() {
myValueSetProvider.myReturnValueSets = new ArrayList<>();
myValueSetProvider.setShouldThrowExceptionForResourceNotFound(false);

Parameters respParam = myClient
.operation()
Expand All @@ -238,70 +227,18 @@ public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown
" - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]");
}

@SuppressWarnings("unused")
private static class MyCodeSystemProvider implements IResourceProvider {
private List<CodeSystem> myReturnCodeSystems;
private Parameters myReturnParams;

@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
return myReturnParams;
}

@Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
assert myReturnCodeSystems != null;
return myReturnCodeSystems;
}
private void setupValueSetValidateCode(String theUrl, String theSystem, String theCode, IBaseParameters theResponseParams) {
ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl);
myValueSetProvider.addTerminologyResource(theSystem);
myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, theResponseParams);

@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
// we currently do this because VersionSpecificWorkerContextWrapper has logic to infer the system when missing
// based on the ValueSet by calling ValidationSupportUtils#extractCodeSystemForCode.
valueSet.getCompose().addInclude().setSystem(theSystem);
}

@SuppressWarnings("unused")
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myReturnParams;
private List<ValueSet> myReturnValueSets;

@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
return myReturnParams;
}

@Search
public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam) {
assert myReturnValueSets != null;
return myReturnValueSets;
}

@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}

private void setupCodeSystemValidateCode(String theUrl, String theCode, IBaseParameters theResponseParams) {
CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl);
myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, theResponseParams);
}
}
Loading

0 comments on commit 3f042fd

Please sign in to comment.