Handle 400 and 404 codes returned by remote terminology operation. (#6151)
* Handle 400 and 404 codes returned by remote terminology operation. * Some simplification * Adjust changelog * Add a comment to explain alternate solution which can be reused.
This commit is contained in:
parent
13ae273cbf
commit
10eaad3cbb
|
@ -1071,8 +1071,9 @@ public interface IValidationSupport {
|
|||
}
|
||||
}
|
||||
|
||||
public void setErrorMessage(String theErrorMessage) {
|
||||
public LookupCodeResult setErrorMessage(String theErrorMessage) {
|
||||
myErrorMessage = theErrorMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
|
|
|
@ -6,6 +6,8 @@ org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.
|
|||
org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.mismatchCodeSystem=Inappropriate CodeSystem URL "{0}" for ValueSet: {1}
|
||||
org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.codeNotFoundInValueSet=Code "{0}" is not in valueset: {1}
|
||||
|
||||
org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.unknownCodeInSystem=Unknown code "{0}#{1}". The Remote Terminology server {2} returned {3}
|
||||
|
||||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.expansionRefersToUnknownCs=Unknown CodeSystem URI "{0}" referenced from ValueSet
|
||||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotYetExpanded=ValueSet "{0}" has not yet been pre-expanded. Performing in-memory expansion without parameters. Current status: {1} | {2}
|
||||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotYetExpanded_OffsetNotAllowed=ValueSet expansion can not combine "offset" with "ValueSet.compose.exclude" unless the ValueSet has been pre-expanded. ValueSet "{0}" must be pre-expanded for this operation to work.
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 6150
|
||||
title: "Previously, the resource $validate operation would return a 404 when the associated profile uses a ValueSet
|
||||
that has multiple includes referencing Remote Terminology CodeSystem resources.
|
||||
This has been fixed to return a 200 with issues instead."
|
|
@ -12,6 +12,8 @@ import ca.uhn.fhir.i18n.Msg;
|
|||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.gclient.IQuery;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
@ -204,9 +206,11 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
FhirContext fhirContext = client.getFhirContext();
|
||||
FhirVersionEnum fhirVersion = fhirContext.getVersion().getVersion();
|
||||
|
||||
switch (fhirVersion) {
|
||||
case DSTU3:
|
||||
case R4:
|
||||
if (fhirVersion.isNewerThan(FhirVersionEnum.R4) || fhirVersion.isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||
throw new UnsupportedOperationException(Msg.code(710) + "Unsupported FHIR version '"
|
||||
+ fhirVersion.getFhirVersionString() + "'. Only DSTU3 and R4 are supported.");
|
||||
}
|
||||
|
||||
IBaseParameters params = ParametersUtil.newInstance(fhirContext);
|
||||
ParametersUtil.addParameterToParametersString(fhirContext, params, "code", code);
|
||||
if (!StringUtils.isEmpty(system)) {
|
||||
|
@ -220,27 +224,38 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
}
|
||||
Class<? extends IBaseResource> codeSystemClass =
|
||||
myCtx.getResourceDefinition("CodeSystem").getImplementingClass();
|
||||
IBaseParameters outcome = client.operation()
|
||||
IBaseParameters outcome;
|
||||
try {
|
||||
outcome = client.operation()
|
||||
.onType(codeSystemClass)
|
||||
.named("$lookup")
|
||||
.withParameters(params)
|
||||
.useHttpGet()
|
||||
.execute();
|
||||
} catch (ResourceNotFoundException | InvalidRequestException e) {
|
||||
// this can potentially be moved to an interceptor and be reused in other areas
|
||||
// where we call a remote server or by the client as a custom interceptor
|
||||
// that interceptor would alter the status code of the response and the body into a different format
|
||||
// e.g. ClientResponseInterceptorModificationTemplate
|
||||
ourLog.error(e.getMessage(), e);
|
||||
LookupCodeResult result = LookupCodeResult.notFound(system, code);
|
||||
result.setErrorMessage(
|
||||
getErrorMessage("unknownCodeInSystem", system, code, client.getServerBase(), e.getMessage()));
|
||||
return result;
|
||||
}
|
||||
if (outcome != null && !outcome.isEmpty()) {
|
||||
switch (fhirVersion) {
|
||||
case DSTU3:
|
||||
return generateLookupCodeResultDstu3(
|
||||
code, system, (org.hl7.fhir.dstu3.model.Parameters) outcome);
|
||||
case R4:
|
||||
if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
return generateLookupCodeResultDstu3(code, system, (org.hl7.fhir.dstu3.model.Parameters) outcome);
|
||||
}
|
||||
if (fhirVersion == FhirVersionEnum.R4) {
|
||||
return generateLookupCodeResultR4(code, system, (Parameters) outcome);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException(Msg.code(710) + "Unsupported FHIR version '"
|
||||
+ fhirVersion.getFhirVersionString() + "'. Only DSTU3 and R4 are supported.");
|
||||
return LookupCodeResult.notFound(system, code);
|
||||
}
|
||||
return null;
|
||||
|
||||
protected String getErrorMessage(String errorCode, Object... theParams) {
|
||||
return getFhirContext().getLocalizer().getMessage(getClass(), errorCode, theParams);
|
||||
}
|
||||
|
||||
private LookupCodeResult generateLookupCodeResultDstu3(
|
||||
|
@ -278,6 +293,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
case "abstract":
|
||||
result.setCodeIsAbstract(Boolean.parseBoolean(parameterTypeAsString));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -384,6 +400,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
case "value":
|
||||
conceptDesignation.setValue(designationComponent.getValue().toString());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return conceptDesignation;
|
||||
|
@ -422,6 +439,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
case "abstract":
|
||||
result.setCodeIsAbstract(Boolean.parseBoolean(parameterTypeAsString));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -508,6 +526,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
case "value":
|
||||
conceptDesignation.setValue(designationComponentValue.toString());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return conceptDesignation;
|
||||
|
@ -591,6 +610,10 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return myBaseUrl;
|
||||
}
|
||||
|
||||
protected CodeValidationResult invokeRemoteValidateCode(
|
||||
String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
|
||||
if (isBlank(theCode)) {
|
||||
|
|
|
@ -17,11 +17,11 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_CODING;
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_GROUP;
|
||||
import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_STRING;
|
||||
import static java.util.stream.IntStream.range;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
@ -54,7 +54,8 @@ public interface ILookupCodeTest {
|
|||
default void lookupCode_forCodeSystemWithBlankCode_throwsException() {
|
||||
try {
|
||||
getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, ""));
|
||||
fail(); } catch (IllegalArgumentException e) {
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("theCode must be provided", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +64,7 @@ public interface ILookupCodeTest {
|
|||
default void lookupCode_forCodeSystemWithPropertyInvalidType_throwsException() {
|
||||
// test
|
||||
LookupCodeResult result = new LookupCodeResult();
|
||||
result.setFound(true);
|
||||
result.getProperties().add(new BaseConceptProperty("someProperty") {
|
||||
public String getType() {
|
||||
return "someUnsupportedType";
|
||||
|
@ -73,7 +75,8 @@ public interface ILookupCodeTest {
|
|||
// test and verify
|
||||
try {
|
||||
getService().lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, null));
|
||||
fail(); } catch (InternalErrorException e) {
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage()).contains("HAPI-1739: Don't know how to handle ");
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +104,7 @@ public interface ILookupCodeTest {
|
|||
ConceptDesignation designation1 = new ConceptDesignation().setUseCode(code1).setUseSystem("system1").setValue("value1").setLanguage("en");
|
||||
ConceptDesignation designation2 = new ConceptDesignation().setUseCode(code2).setUseSystem("system2").setValue("value2").setLanguage("es");
|
||||
LookupCodeResult result = new LookupCodeResult();
|
||||
result.setFound(true);
|
||||
result.getDesignations().add(designation1);
|
||||
result.getDesignations().add(designation2);
|
||||
getCodeSystemProvider().setLookupCodeResult(result);
|
||||
|
@ -184,6 +188,8 @@ public interface ILookupCodeTest {
|
|||
assertNotNull(outcome);
|
||||
assertEquals(theRequest.getCode(), getCodeSystemProvider().getCode());
|
||||
assertEquals(theRequest.getSystem(), getCodeSystemProvider().getSystem());
|
||||
assertEquals(theExpectedResult.isFound(), outcome.isFound());
|
||||
assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage());
|
||||
assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName());
|
||||
assertEquals(theExpectedResult.getCodeDisplay(), outcome.getCodeDisplay());
|
||||
assertEquals(theExpectedResult.getCodeSystemVersion(), outcome.getCodeSystemVersion());
|
||||
|
@ -199,6 +205,7 @@ public interface ILookupCodeTest {
|
|||
default void verifyLookupWithConceptDesignation(final ConceptDesignation theConceptDesignation) {
|
||||
// setup
|
||||
LookupCodeResult result = new LookupCodeResult();
|
||||
result.setFound(true);
|
||||
result.getDesignations().add(theConceptDesignation);
|
||||
getCodeSystemProvider().setLookupCodeResult(result);
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package org.hl7.fhir.common.hapi.validation;
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
|
||||
import ca.uhn.fhir.context.support.LookupCodeRequest;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
/**
|
||||
* Additional tests specific for Remote Terminology $lookup operation.
|
||||
* Please see base interface for additional tests, implementation agnostic.
|
||||
*/
|
||||
public interface IRemoteTerminologyLookupCodeTest extends ILookupCodeTest {
|
||||
|
||||
String MESSAGE_RESPONSE_NOT_FOUND = "Code {0} was not found";
|
||||
String MESSAGE_RESPONSE_INVALID = "Code {0} lookup is missing a system";
|
||||
|
||||
@Override
|
||||
RemoteTerminologyServiceValidationSupport getService();
|
||||
|
||||
@Test
|
||||
default void lookupCode_forCodeSystemWithCodeNotFound_returnsNotFound() {
|
||||
String baseUrl = getService().getBaseUrl();
|
||||
final String codeNotFound = "a";
|
||||
final String system = CODE_SYSTEM;
|
||||
final String codeAndSystem = system + "#" + codeNotFound;
|
||||
final String exceptionMessage = MessageFormat.format(MESSAGE_RESPONSE_NOT_FOUND, codeNotFound);
|
||||
LookupCodeResult result = new LookupCodeResult()
|
||||
.setFound(false)
|
||||
.setSearchedForCode(codeNotFound)
|
||||
.setSearchedForSystem(system)
|
||||
.setErrorMessage("Unknown code \"" + codeAndSystem + "\". The Remote Terminology server " + baseUrl + " returned HTTP 404 Not Found: " + exceptionMessage);
|
||||
getCodeSystemProvider().setLookupCodeResult(result);
|
||||
|
||||
LookupCodeRequest request = new LookupCodeRequest(system, codeNotFound, null, null);
|
||||
verifyLookupCodeResult(request, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
default void lookupCode_forCodeSystemWithInvalidRequest_returnsNotFound() {
|
||||
String baseUrl = getService().getBaseUrl();
|
||||
final String codeNotFound = "a";
|
||||
final String system = null;
|
||||
final String codeAndSystem = system + "#" + codeNotFound;
|
||||
final String exceptionMessage = MessageFormat.format(MESSAGE_RESPONSE_INVALID, codeNotFound);
|
||||
LookupCodeResult result = new LookupCodeResult()
|
||||
.setFound(false)
|
||||
.setSearchedForCode(codeNotFound)
|
||||
.setSearchedForSystem(system)
|
||||
.setErrorMessage("Unknown code \"" + codeAndSystem + "\". The Remote Terminology server " + baseUrl + " returned HTTP 400 Bad Request: " + exceptionMessage);
|
||||
getCodeSystemProvider().setLookupCodeResult(result);
|
||||
|
||||
LookupCodeRequest request = new LookupCodeRequest(system, codeNotFound, null, null);
|
||||
verifyLookupCodeResult(request, result);
|
||||
}
|
||||
}
|
|
@ -9,9 +9,11 @@ import ca.uhn.fhir.rest.annotation.Operation;
|
|||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.hl7.fhir.common.hapi.validation.ILookupCodeTest;
|
||||
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.hl7.fhir.dstu3.model.BooleanType;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
|
@ -33,6 +35,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -41,7 +44,7 @@ import java.util.stream.Stream;
|
|||
* Version specific tests for CodeSystem $lookup against RemoteTerminologyValidationSupport.
|
||||
* @see RemoteTerminologyServiceValidationSupport
|
||||
*/
|
||||
public class RemoteTerminologyLookupCodeDstu3Test implements ILookupCodeTest {
|
||||
public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyLookupCodeTest {
|
||||
private static final FhirContext ourCtx = FhirContext.forDstu3Cached();
|
||||
@RegisterExtension
|
||||
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
|
||||
|
@ -181,6 +184,12 @@ public class RemoteTerminologyLookupCodeDstu3Test implements ILookupCodeTest {
|
|||
) {
|
||||
myCode = theCode;
|
||||
mySystemUrl = theSystem;
|
||||
if (theSystem == null) {
|
||||
throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode));
|
||||
}
|
||||
if (!myLookupCodeResult.isFound()) {
|
||||
throw new ResourceNotFoundException(MessageFormat.format(MESSAGE_RESPONSE_NOT_FOUND, theCode));
|
||||
}
|
||||
return myLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,9 +7,11 @@ import ca.uhn.fhir.rest.annotation.Operation;
|
|||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.hl7.fhir.common.hapi.validation.ILookupCodeTest;
|
||||
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest;
|
||||
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||
|
@ -31,6 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -42,7 +45,7 @@ import static ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
|
|||
* Version specific tests for CodeSystem $lookup against RemoteTerminologyValidationSupport.
|
||||
* @see RemoteTerminologyServiceValidationSupport
|
||||
*/
|
||||
public class RemoteTerminologyLookupCodeR4Test implements ILookupCodeTest {
|
||||
public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLookupCodeTest {
|
||||
private static final FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
@RegisterExtension
|
||||
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
|
||||
|
@ -181,6 +184,12 @@ public class RemoteTerminologyLookupCodeR4Test implements ILookupCodeTest {
|
|||
) {
|
||||
myCode = theCode;
|
||||
mySystemUrl = theSystem;
|
||||
if (theSystem == null) {
|
||||
throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode));
|
||||
}
|
||||
if (!myLookupCodeResult.isFound()) {
|
||||
throw new ResourceNotFoundException(MessageFormat.format(MESSAGE_RESPONSE_NOT_FOUND, theCode));
|
||||
}
|
||||
return myLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames);
|
||||
}
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue