Merge branch 'rel_7_6' into br-20241104-cr-hotfix

This commit is contained in:
Brenin Rhodes 2024-11-07 09:55:23 -07:00 committed by GitHub
commit 7373495cf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 2936 additions and 786 deletions

View File

@ -440,74 +440,259 @@ public interface IValidationSupport {
return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support";
}
/**
* Defines codes in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
*/
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
enum IssueSeverity {
/**
* The issue caused the action to fail, and no further checking could be performed.
*/
FATAL,
FATAL("fatal"),
/**
* The issue is sufficiently important to cause the action to fail.
*/
ERROR,
ERROR("error"),
/**
* The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
*/
WARNING,
WARNING("warning"),
/**
* The issue has no relation to the degree of success of the action.
*/
INFORMATION
INFORMATION("information"),
/**
* The operation was successful.
*/
SUCCESS("success");
// the spec for OperationOutcome mentions that a code from http://hl7.org/fhir/issue-severity is required
private final String myCode;
IssueSeverity(String theCode) {
myCode = theCode;
}
/**
* Provide mapping to a code in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
* @return the code
*/
public String getCode() {
return myCode;
}
/**
* Creates a {@link IssueSeverity} object from the given code.
* @return the {@link IssueSeverity}
*/
public static IssueSeverity fromCode(String theCode) {
switch (theCode) {
case "fatal":
return FATAL;
case "error":
return ERROR;
case "warning":
return WARNING;
case "information":
return INFORMATION;
case "success":
return SUCCESS;
default:
return null;
}
}
}
enum CodeValidationIssueCode {
NOT_FOUND,
CODE_INVALID,
INVALID,
OTHER
}
/**
* Defines codes in system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
* The binding is enforced as a part of validation logic in the FHIR Core Validation library where an exception is thrown.
* Only a sub-set of these codes are defined as constants because they relate to validation,
* If there are additional ones that come up, for Remote Terminology they are currently supported via
* {@link IValidationSupport.CodeValidationIssue#CodeValidationIssue(String, IssueSeverity, String)}
* while for internal validators, more constants can be added to make things easier and consistent.
* This maps to resource OperationOutcome.issue.code.
*/
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
class CodeValidationIssueCode {
public static final CodeValidationIssueCode NOT_FOUND = new CodeValidationIssueCode("not-found");
public static final CodeValidationIssueCode CODE_INVALID = new CodeValidationIssueCode("code-invalid");
public static final CodeValidationIssueCode INVALID = new CodeValidationIssueCode("invalid");
enum CodeValidationIssueCoding {
VS_INVALID,
NOT_FOUND,
NOT_IN_VS,
private final String myCode;
INVALID_CODE,
INVALID_DISPLAY,
OTHER
}
class CodeValidationIssue {
private final String myMessage;
private final IssueSeverity mySeverity;
private final CodeValidationIssueCode myCode;
private final CodeValidationIssueCoding myCoding;
public CodeValidationIssue(
String theMessage,
IssueSeverity mySeverity,
CodeValidationIssueCode theCode,
CodeValidationIssueCoding theCoding) {
this.myMessage = theMessage;
this.mySeverity = mySeverity;
this.myCode = theCode;
this.myCoding = theCoding;
// this is intentionally not exposed
CodeValidationIssueCode(String theCode) {
myCode = theCode;
}
/**
* Retrieve the corresponding code from system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
* @return the code
*/
public String getCode() {
return myCode;
}
}
/**
* Holds information about the details of a {@link CodeValidationIssue}.
* This maps to resource OperationOutcome.issue.details.
*/
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
class CodeValidationIssueDetails {
private final String myText;
private List<CodeValidationIssueCoding> myCodings;
public CodeValidationIssueDetails(String theText) {
myText = theText;
}
// intentionally not exposed
void addCoding(CodeValidationIssueCoding theCoding) {
getCodings().add(theCoding);
}
public CodeValidationIssueDetails addCoding(String theSystem, String theCode) {
if (myCodings == null) {
myCodings = new ArrayList<>();
}
myCodings.add(new CodeValidationIssueCoding(theSystem, theCode));
return this;
}
public String getText() {
return myText;
}
public List<CodeValidationIssueCoding> getCodings() {
if (myCodings == null) {
myCodings = new ArrayList<>();
}
return myCodings;
}
}
/**
* Defines codes that can be part of the details of an issue.
* There are some constants available (pre-defined) for codes for system <a href="http://hl7.org/fhir/tools/CodeSystem/tx-issue-type">http://hl7.org/fhir/tools/CodeSystem/tx-issue-type</a>.
* This maps to resource OperationOutcome.issue.details.coding[0].code.
*/
class CodeValidationIssueCoding {
public static String TX_ISSUE_SYSTEM = "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type";
public static CodeValidationIssueCoding VS_INVALID =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-invalid");
public static final CodeValidationIssueCoding NOT_FOUND =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-found");
public static final CodeValidationIssueCoding NOT_IN_VS =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-in-vs");
public static final CodeValidationIssueCoding INVALID_CODE =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "invalid-code");
public static final CodeValidationIssueCoding INVALID_DISPLAY =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-display");
private final String mySystem, myCode;
// this is intentionally not exposed
CodeValidationIssueCoding(String theSystem, String theCode) {
mySystem = theSystem;
myCode = theCode;
}
/**
* Retrieve the corresponding code for the details of a validation issue.
* @return the code
*/
public String getCode() {
return myCode;
}
/**
* Retrieve the system for the details of a validation issue.
* @return the system
*/
public String getSystem() {
return mySystem;
}
}
/**
* This is a hapi-fhir internal version agnostic object holding information about a validation issue.
* An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead.
*/
class CodeValidationIssue {
private final String myDiagnostics;
private final IssueSeverity mySeverity;
private final CodeValidationIssueCode myCode;
private CodeValidationIssueDetails myDetails;
public CodeValidationIssue(
String theDiagnostics, IssueSeverity theSeverity, CodeValidationIssueCode theTypeCode) {
this(theDiagnostics, theSeverity, theTypeCode, null);
}
public CodeValidationIssue(String theDiagnostics, IssueSeverity theSeverity, String theTypeCode) {
this(theDiagnostics, theSeverity, new CodeValidationIssueCode(theTypeCode), null);
}
public CodeValidationIssue(
String theDiagnostics,
IssueSeverity theSeverity,
CodeValidationIssueCode theType,
CodeValidationIssueCoding theDetailsCoding) {
myDiagnostics = theDiagnostics;
mySeverity = theSeverity;
myCode = theType;
// reuse the diagnostics message as a detail text message
myDetails = new CodeValidationIssueDetails(theDiagnostics);
myDetails.addCoding(theDetailsCoding);
}
/**
* @deprecated Please use {@link #getDiagnostics()} instead.
*/
@Deprecated(since = "7.4.6")
public String getMessage() {
return myMessage;
return getDiagnostics();
}
public String getDiagnostics() {
return myDiagnostics;
}
public IssueSeverity getSeverity() {
return mySeverity;
}
/**
* @deprecated Please use {@link #getType()} instead.
*/
@Deprecated(since = "7.4.6")
public CodeValidationIssueCode getCode() {
return getType();
}
public CodeValidationIssueCode getType() {
return myCode;
}
/**
* @deprecated Please use {@link #getDetails()} instead. That has support for multiple codings.
*/
@Deprecated(since = "7.4.6")
public CodeValidationIssueCoding getCoding() {
return myCoding;
return myDetails != null
? myDetails.getCodings().stream().findFirst().orElse(null)
: null;
}
public void setDetails(CodeValidationIssueDetails theDetails) {
this.myDetails = theDetails;
}
public CodeValidationIssueDetails getDetails() {
return myDetails;
}
public boolean hasIssueDetailCode(@Nonnull String theCode) {
// this method is system agnostic at the moment but it can be restricted if needed
return myDetails.getCodings().stream().anyMatch(coding -> theCode.equals(coding.getCode()));
}
}
@ -671,6 +856,10 @@ public interface IValidationSupport {
}
}
/**
* This is a hapi-fhir internal version agnostic object holding information about the validation result.
* An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult.
*/
class CodeValidationResult {
public static final String SOURCE_DETAILS = "sourceDetails";
public static final String RESULT = "result";
@ -686,7 +875,7 @@ public interface IValidationSupport {
private String myDisplay;
private String mySourceDetails;
private List<CodeValidationIssue> myCodeValidationIssues;
private List<CodeValidationIssue> myIssues;
public CodeValidationResult() {
super();
@ -771,20 +960,45 @@ public interface IValidationSupport {
return this;
}
/**
* @deprecated Please use method {@link #getIssues()} instead.
*/
@Deprecated(since = "7.4.6")
public List<CodeValidationIssue> getCodeValidationIssues() {
if (myCodeValidationIssues == null) {
myCodeValidationIssues = new ArrayList<>();
}
return myCodeValidationIssues;
return getIssues();
}
/**
* @deprecated Please use method {@link #setIssues(List)} instead.
*/
@Deprecated(since = "7.4.6")
public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) {
myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues);
return setIssues(theCodeValidationIssues);
}
/**
* @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead.
*/
@Deprecated(since = "7.4.6")
public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
getCodeValidationIssues().add(theCodeValidationIssue);
return this;
}
public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
getCodeValidationIssues().add(theCodeValidationIssue);
public List<CodeValidationIssue> getIssues() {
if (myIssues == null) {
myIssues = new ArrayList<>();
}
return myIssues;
}
public CodeValidationResult setIssues(List<CodeValidationIssue> theIssues) {
myIssues = new ArrayList<>(theIssues);
return this;
}
public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) {
getIssues().add(theCodeValidationIssue);
return this;
}
@ -811,17 +1025,19 @@ public interface IValidationSupport {
public String getSeverityCode() {
String retVal = null;
if (getSeverity() != null) {
retVal = getSeverity().name().toLowerCase();
retVal = getSeverity().getCode();
}
return retVal;
}
/**
* Sets an issue severity as a string code. Value must be the name of
* one of the enum values in {@link IssueSeverity}. Value is case-insensitive.
* Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead.
* @param theSeverityCode the code
* @return the current {@link CodeValidationResult} instance
*/
public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) {
setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase()));
@Deprecated(since = "7.4.6")
public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) {
setSeverity(IssueSeverity.fromCode(theSeverityCode));
return this;
}
@ -838,6 +1054,11 @@ public interface IValidationSupport {
if (isNotBlank(getSourceDetails())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
}
/*
should translate issues as well, except that is version specific code, so it requires more refactoring
or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult
@see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation
*/
return retVal;
}

View File

@ -28,6 +28,8 @@ import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import com.google.common.annotations.Beta;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseParameters;
@ -231,6 +233,23 @@ public interface Repository {
// Querying starts here
/**
* Searches this repository
*
* @see <a href="https://www.hl7.org/fhir/http.html#search">FHIR search</a>
*
* @param <B> a Bundle type
* @param <T> a Resource type
* @param bundleType the class of the Bundle type to return
* @param resourceType the class of the Resource type to search
* @param searchParameters the searchParameters for this search
* @return a Bundle with the results of the search
*/
default <B extends IBaseBundle, T extends IBaseResource> B search(
Class<B> bundleType, Class<T> resourceType, Multimap<String, List<IQueryParameterType>> searchParameters) {
return this.search(bundleType, resourceType, searchParameters, Collections.emptyMap());
}
/**
* Searches this repository
*
@ -264,9 +283,32 @@ public interface Repository {
<B extends IBaseBundle, T extends IBaseResource> B search(
Class<B> bundleType,
Class<T> resourceType,
Map<String, List<IQueryParameterType>> searchParameters,
Multimap<String, List<IQueryParameterType>> searchParameters,
Map<String, String> headers);
/**
* Searches this repository
*
* @see <a href="https://www.hl7.org/fhir/http.html#search">FHIR search</a>
*
* @param <B> a Bundle type
* @param <T> a Resource type
* @param bundleType the class of the Bundle type to return
* @param resourceType the class of the Resource type to search
* @param searchParameters the searchParameters for this search
* @param headers headers for this request, typically key-value pairs of HTTP headers
* @return a Bundle with the results of the search
*/
default <B extends IBaseBundle, T extends IBaseResource> B search(
Class<B> bundleType,
Class<T> resourceType,
Map<String, List<IQueryParameterType>> searchParameters,
Map<String, String> headers) {
ArrayListMultimap<String, List<IQueryParameterType>> multimap = ArrayListMultimap.create();
searchParameters.forEach(multimap::put);
return this.search(bundleType, resourceType, multimap, headers);
}
// Paging starts here
/**

View File

@ -0,0 +1,43 @@
package ca.uhn.fhir.util.adapters;
import jakarta.annotation.Nonnull;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
public class AdapterManager implements IAdapterManager {
public static final AdapterManager INSTANCE = new AdapterManager();
Set<IAdapterFactory> myAdapterFactories = new HashSet<>();
/**
* Hidden to force shared use of the public INSTANCE.
*/
AdapterManager() {}
public <T> @Nonnull Optional<T> getAdapter(Object theObject, Class<T> theTargetType) {
// todo this can be sped up with a cache of type->Factory.
return myAdapterFactories.stream()
.filter(nextFactory -> nextFactory.getAdapters().stream().anyMatch(theTargetType::isAssignableFrom))
.flatMap(nextFactory -> {
var adapter = nextFactory.getAdapter(theObject, theTargetType);
// can't use Optional.stream() because of our Android target is API level 26/JDK 8.
if (adapter.isPresent()) {
return Stream.of(adapter.get());
} else {
return Stream.empty();
}
})
.findFirst();
}
public void registerFactory(@Nonnull IAdapterFactory theFactory) {
myAdapterFactories.add(theFactory);
}
public void unregisterFactory(@Nonnull IAdapterFactory theFactory) {
myAdapterFactories.remove(theFactory);
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.util.adapters;
import java.util.Optional;
public class AdapterUtils {
/**
* Main entry point for adapter calls.
* Implements three conversions: cast to the target type, use IAdaptable if present, or lastly try the AdapterManager.INSTANCE.
* @param theObject the object to be adapted
* @param theTargetType the type of the adapter requested
*/
static <T> Optional<T> adapt(Object theObject, Class<T> theTargetType) {
if (theTargetType.isInstance(theObject)) {
//noinspection unchecked
return Optional.of((T) theObject);
}
if (theObject instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) theObject;
var adapted = adaptable.getAdapter(theTargetType);
if (adapted.isPresent()) {
return adapted;
}
}
return AdapterManager.INSTANCE.getAdapter(theObject, theTargetType);
}
}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.util.adapters;
import jakarta.annotation.Nonnull;
import java.util.Optional;
/**
* Generic version of Eclipse IAdaptable interface.
*/
public interface IAdaptable {
/**
* Get an adapter of requested type.
* @param theTargetType the desired type of the adapter
* @return an adapter of theTargetType if possible, or empty.
*/
default <T> @Nonnull Optional<T> getAdapter(@Nonnull Class<T> theTargetType) {
return AdapterUtils.adapt(this, theTargetType);
}
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.util.adapters;
import java.util.Collection;
import java.util.Optional;
/**
* Interface for external service that builds adaptors for targets.
*/
public interface IAdapterFactory {
/**
* Build an adaptor for the target.
* May return empty() even if the target type is listed in getAdapters() when
* the factory fails to convert a particular instance.
*
* @param theObject the object to be adapted.
* @param theAdapterType the target type
* @return the adapter, if possible.
*/
<T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType);
/**
* @return the collection of adapter target types handled by this factory.
*/
Collection<Class<?>> getAdapters();
}

View File

@ -0,0 +1,10 @@
package ca.uhn.fhir.util.adapters;
import java.util.Optional;
/**
* Get an adaptor
*/
public interface IAdapterManager {
<T> Optional<T> getAdapter(Object theTarget, Class<T> theAdapter);
}

View File

@ -0,0 +1,20 @@
/**
* Implements the Adapter pattern to allow external classes to extend/adapt existing classes.
* Useful for extending interfaces that are closed to modification, or restricted for classpath reasons.
* <p>
* For clients, the main entry point is {@link ca.uhn.fhir.util.adapters.AdapterUtils#adapt(java.lang.Object, java.lang.Class)}
* which will attempt to cast to the target type, or build an adapter of the target type.
* </p>
* <p>
* For implementors, you can support adaptation via two mechanisms:
* <ul>
* <li>by implementing {@link ca.uhn.fhir.util.adapters.IAdaptable} directly on a class to provide supported adapters,
* <li>or when the class is closed to direct modification, you can implement
* an instance of {@link ca.uhn.fhir.util.adapters.IAdapterFactory} and register
* it with the public {@link ca.uhn.fhir.util.adapters.AdapterManager#INSTANCE}.</li>
* </ul>
* The AdapterUtils.adapt() supports both of these.
* </p>
* Inspired by the Eclipse runtime.
*/
package ca.uhn.fhir.util.adapters;

View File

@ -0,0 +1,78 @@
package ca.uhn.fhir.util.adapters;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class AdapterManagerTest {
AdapterManager myAdapterManager = new AdapterManager();
@AfterAll
static void tearDown() {
assertThat(AdapterManager.INSTANCE.myAdapterFactories)
.withFailMessage("Don't dirty the public instance").isEmpty();
}
@Test
void testRegisterFactory_providesAdapter() {
// given
myAdapterManager.registerFactory(new StringToIntFactory());
// when
var result = myAdapterManager.getAdapter("22", Integer.class);
// then
assertThat(result).contains(22);
}
@Test
void testRegisterFactory_wrongTypeStillEmpty() {
// given
myAdapterManager.registerFactory(new StringToIntFactory());
// when
var result = myAdapterManager.getAdapter("22", Float.class);
// then
assertThat(result).isEmpty();
}
@Test
void testUnregisterFactory_providesEmpty() {
// given active factory, now gone.
StringToIntFactory factory = new StringToIntFactory();
myAdapterManager.registerFactory(factory);
myAdapterManager.getAdapter("22", Integer.class);
myAdapterManager.unregisterFactory(factory);
// when
var result = myAdapterManager.getAdapter("22", Integer.class);
// then
assertThat(result).isEmpty();
}
static class StringToIntFactory implements IAdapterFactory {
@Override
public <T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType) {
if (theObject instanceof String s) {
if (theAdapterType.isAssignableFrom(Integer.class)) {
@SuppressWarnings("unchecked")
T i = (T) Integer.valueOf(s);
return Optional.of(i);
}
}
return Optional.empty();
}
public Collection<Class<?>> getAdapters() {
return List.of(Integer.class);
}
}
}

View File

@ -0,0 +1,123 @@
package ca.uhn.fhir.util.adapters;
import jakarta.annotation.Nonnull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
class AdapterUtilsTest {
final private IAdapterFactory myTestFactory = new TestAdaptorFactory();
@AfterEach
void tearDown() {
AdapterManager.INSTANCE.unregisterFactory(myTestFactory);
}
@Test
void testNullDoesNotAdapt() {
// when
var adapted = AdapterUtils.adapt(null, InterfaceA.class);
// then
assertThat(adapted).isEmpty();
}
@Test
void testAdaptObjectImplementingInterface() {
// given
var object = new ClassB();
// when
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
// then
assertThat(adapted)
.isPresent()
.get().isInstanceOf(InterfaceA.class);
assertThat(adapted.get()).withFailMessage("Use object since it implements interface").isSameAs(object);
}
@Test
void testAdaptObjectImplementingAdaptorSupportingInterface() {
// given
var object = new SelfAdaptableClass();
// when
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
// then
assertThat(adapted)
.isPresent()
.get().isInstanceOf(InterfaceA.class);
}
@Test
void testAdaptObjectViaAdapterManager() {
// given
var object = new ManagerAdaptableClass();
AdapterManager.INSTANCE.registerFactory(myTestFactory);
// when
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
// then
assertThat(adapted)
.isPresent()
.get().isInstanceOf(InterfaceA.class);
}
interface InterfaceA {
}
static class ClassB implements InterfaceA {
}
/** class that can adapt itself to IAdaptable */
static class SelfAdaptableClass implements IAdaptable {
@Nonnull
@Override
public <T> Optional<T> getAdapter(@Nonnull Class<T> theTargetType) {
if (theTargetType.isAssignableFrom(InterfaceA.class)) {
T value = theTargetType.cast(buildInterfaceAWrapper(this));
return Optional.of(value);
}
return Optional.empty();
}
}
private static @Nonnull InterfaceA buildInterfaceAWrapper(Object theObject) {
return new InterfaceA() {};
}
/** Class that relies on an external IAdapterFactory */
static class ManagerAdaptableClass {
}
static class TestAdaptorFactory implements IAdapterFactory {
@Override
public <T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType) {
if (theObject instanceof ManagerAdaptableClass && theAdapterType == InterfaceA.class) {
T adapter = theAdapterType.cast(buildInterfaceAWrapper(theObject));
return Optional.of(adapter);
}
return Optional.empty();
}
@Override
public Collection<Class<?>> getAdapters() {
return Set.of(InterfaceA.class);
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
---
type: fix
issue: 6440
title: "Previously, if an `IInterceptorBroadcaster` was set in a `RequestDetails` object,
`STORAGE_PRECHECK_FOR_CACHED_SEARCH` hooks that were registered to that `IInterceptorBroadcaster` were not
called. Also, if an `IInterceptorBroadcaster` was set in the `RequestDetails` object, the boolean return value of the hooks
registered to that `IInterceptorBroadcaster` were not taken into account. This second issue existed for all pointcuts
that returned a boolean type, not just for `STORAGE_PRECHECK_FOR_CACHED_SEARCH`. These issues have now been fixed."

View File

@ -0,0 +1,4 @@
---
type: add
issue: 6445
title: "Add Multimap versions of the search() methods to Repository to support queries like `Patient?_tag=a&_tag=b`"

View File

@ -600,12 +600,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
.add(SearchParameterMap.class, theParams)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
Object outcome = CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(
boolean canUseCache = CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster,
theRequestDetails,
Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH,
params);
if (Boolean.FALSE.equals(outcome)) {
if (!canUseCache) {
return null;
}

View File

@ -1044,7 +1044,8 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
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);
@ -2190,7 +2191,7 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
.setSeverity(IssueSeverity.ERROR)
.setCodeSystemVersion(theCodeSystemVersion)
.setMessage(theMessage)
.addCodeValidationIssue(new CodeValidationIssue(
.addIssue(new CodeValidationIssue(
theMessage,
IssueSeverity.ERROR,
CodeValidationIssueCode.CODE_INVALID,

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.mdm.helper;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.util.function.Supplier;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
/**
@ -78,6 +80,7 @@ public abstract class BaseMdmHelper implements BeforeEachCallback, AfterEachCall
//they are coming from an external HTTP Request.
MockitoAnnotations.initMocks(this);
when(myMockSrd.getInterceptorBroadcaster()).thenReturn(myMockInterceptorBroadcaster);
when(myMockInterceptorBroadcaster.callHooks(any(Pointcut.class), any(HookParams.class))).thenReturn(true);
when(myMockSrd.getServletRequest()).thenReturn(myMockServletRequest);
when(myMockSrd.getServer()).thenReturn(myMockRestfulServer);
when(myMockSrd.getRequestId()).thenReturn("MOCK_REQUEST");

View File

@ -62,6 +62,9 @@ public abstract class BaseComboParamsR4Test extends BaseJpaR4Test {
myMessages.add("REUSING CACHED SEARCH");
return null;
});
// allow searches to use cached results
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH), ArgumentMatchers.any(HookParams.class))).thenReturn(true);
}
@AfterEach

View File

@ -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;
@ -33,9 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
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;
@ -43,15 +33,15 @@ 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";
@ -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)
@ -76,8 +66,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
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);
}
@ -103,11 +93,11 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
@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();
@ -115,8 +105,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
.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);
@ -128,7 +118,7 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
@Test
public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() {
myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.setShouldThrowExceptionForResourceNotFound(false);
Parameters respParam = myClient
.operation()
@ -166,21 +156,21 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
@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();
@ -193,21 +183,20 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
@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);
@ -219,7 +208,7 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
@Test
public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() {
myValueSetProvider.myReturnValueSets = new ArrayList<>();
myValueSetProvider.setShouldThrowExceptionForResourceNotFound(false);
Parameters respParam = myClient
.operation()
@ -238,70 +227,18 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour
" - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]");
}
@SuppressWarnings("unused")
private static class MyCodeSystemProvider implements IResourceProvider {
private List<CodeSystem> myReturnCodeSystems;
private Parameters myReturnParams;
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);
@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;
}
@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);
}
}

View File

@ -0,0 +1,261 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
import ca.uhn.fhir.util.ClasspathUtil;
import org.apache.commons.lang3.StringUtils;
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.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Procedure;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.List;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests resource validation with Remote Terminology bindings.
* To create a new test, you need to do 3 things:
* (1) the resource profile, if any custom one is needed should be stored in the FHIR repository
* (2) all the CodeSystem and ValueSet terminology resources need to be added to the corresponding resource provider.
* At the moment only placeholder CodeSystem/ValueSet resources are returned with id and url populated. For the moment
* there was no need to load the full resource, but that can be done if there is logic run which requires it.
* This is a minimal setup.
* (3) the Remote Terminology operation responses that are needed for the test need to be added to the corresponding
* resource provider. The intention is to record and use the responses of an actual terminology server
* e.g. <a href="https://r4.ontoserver.csiro.au/fhir/">OntoServer</a>.
* This is done as a result of the fact that unit test cannot always catch bugs which are introduced as a result of
* changes in the OntoServer or FHIR Validator library, or both.
* @see #setupValueSetValidateCode
* @see #setupCodeSystemValidateCode
* The responses are in Parameters resource format where issues is an OperationOutcome resource.
*/
public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Test {
private static final FhirContext ourCtx = FhirContext.forR4Cached();
@RegisterExtension
protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private RemoteTerminologyServiceValidationSupport mySvc;
@Autowired
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
private ValidationSupportChain myValidationSupportChain;
private IValidationProviders.MyValidationProvider<CodeSystem> myCodeSystemProvider;
private IValidationProviders.MyValidationProvider<ValueSet> myValueSetProvider;
@BeforeEach
public void before() {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
myValidationSupportChain.addValidationSupport(0, mySvc);
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4();
ourRestfulServerExtension.registerProvider(myCodeSystemProvider);
ourRestfulServerExtension.registerProvider(myValueSetProvider);
}
@AfterEach
public void after() {
myValidationSupportChain.removeValidationSupport(mySvc);
ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
ourRestfulServerExtension.unregisterProvider(myCodeSystemProvider);
ourRestfulServerExtension.unregisterProvider(myValueSetProvider);
}
@Test
public void validate_withProfileWithValidCodesFromAllBindingTypes_returnsNoErrors() {
// setup
final StructureDefinition profileEncounter = ClasspathUtil.loadResource(ourCtx, StructureDefinition.class, "validation/encounter/profile-encounter-custom.json");
myClient.update().resource(profileEncounter).execute();
final String statusCode = "planned";
final String classCode = "IMP";
final String identifierTypeCode = "VN";
final String statusSystem = "http://hl7.org/fhir/encounter-status"; // implied system
final String classSystem = "http://terminology.hl7.org/CodeSystem/v3-ActCode";
final String identifierTypeSystem = "http://terminology.hl7.org/CodeSystem/v2-0203";
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/encounter-status", "http://hl7.org/fhir/encounter-status", statusCode, "validation/encounter/validateCode-ValueSet-encounter-status.json");
setupValueSetValidateCode("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode", "http://terminology.hl7.org/CodeSystem/v3-ActCode", classCode, "validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json");
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/identifier-type", "http://hl7.org/fhir/identifier-type", identifierTypeCode, "validation/encounter/validateCode-ValueSet-identifier-type.json");
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/encounter/validateCode-CodeSystem-encounter-status.json");
setupCodeSystemValidateCode(classSystem, classCode, "validation/encounter/validateCode-CodeSystem-v3-ActCode.json");
setupCodeSystemValidateCode(identifierTypeSystem, identifierTypeCode, "validation/encounter/validateCode-CodeSystem-v2-0203.json");
Encounter encounter = new Encounter();
encounter.getMeta().addProfile("http://example.ca/fhir/StructureDefinition/profile-encounter");
// required binding
encounter.setStatus(Encounter.EncounterStatus.fromCode(statusCode));
// preferred binding
encounter.getClass_()
.setSystem(classSystem)
.setCode(classCode)
.setDisplay("inpatient encounter");
// extensible binding
encounter.addIdentifier()
.getType().addCoding()
.setSystem(identifierTypeSystem)
.setCode(identifierTypeCode)
.setDisplay("Visit number");
// execute
List<String> errors = getValidationErrors(encounter);
// verify
assertThat(errors).isEmpty();
}
@Test
public void validate_withInvalidCode_returnsErrors() {
// setup
final String statusCode = "final";
final String code = "10xx";
final String statusSystem = "http://hl7.org/fhir/observation-status";
final String loincSystem = "http://loinc.org";
final String system = "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM";
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-status", statusSystem, statusCode, "validation/observation/validateCode-ValueSet-observation-status.json");
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-codes", loincSystem, statusCode, "validation/observation/validateCode-ValueSet-codes.json");
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/observation/validateCode-CodeSystem-observation-status.json");
setupCodeSystemValidateCode(system, code, "validation/observation/validateCode-CodeSystem-ICD9CM.json");
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.fromCode(statusCode));
obs.getCode().addCoding().setCode(code).setSystem(system);
// execute
List<String> errors = getValidationErrors(obs);
assertThat(errors).hasSize(1);
// verify
assertThat(errors.get(0))
.contains("Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM");
}
@Test
public void validate_withProfileWithInvalidCode_returnsErrors() {
// setup
String profile = "http://example.ca/fhir/StructureDefinition/profile-procedure";
StructureDefinition profileProcedure = ClasspathUtil.loadResource(myFhirContext, StructureDefinition.class, "validation/procedure/profile-procedure.json");
myClient.update().resource(profileProcedure).execute();
final String statusCode = "completed";
final String procedureCode1 = "417005";
final String procedureCode2 = "xx417005";
final String statusSystem = "http://hl7.org/fhir/event-status";
final String snomedSystem = "http://snomed.info/sct";
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode1, "validation/procedure/validateCode-ValueSet-procedure-code-valid.json");
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode2, "validation/procedure/validateCode-ValueSet-procedure-code-invalid.json");
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/procedure/validateCode-CodeSystem-event-status.json");
setupCodeSystemValidateCode(snomedSystem, procedureCode1, "validation/procedure/validateCode-CodeSystem-snomed-valid.json");
setupCodeSystemValidateCode(snomedSystem, procedureCode2, "validation/procedure/validateCode-CodeSystem-snomed-invalid.json");
Procedure procedure = new Procedure();
procedure.setSubject(new Reference("Patient/P1"));
procedure.setStatus(Procedure.ProcedureStatus.fromCode(statusCode));
procedure.getCode().addCoding().setSystem(snomedSystem).setCode(procedureCode1);
procedure.getCode().addCoding().setSystem(snomedSystem).setCode(procedureCode2);
procedure.getMeta().addProfile(profile);
// execute
List<String> errors = getValidationErrors(procedure);
// TODO: there is currently some duplication in the errors returned. This needs to be investigated and fixed.
// assertThat(errors).hasSize(1);
// verify
// note that we're not selecting an explicit versions (using latest) so the message verification does not include it.
assertThat(StringUtils.join("", errors))
.contains("Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct'")
.doesNotContain("The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code")
.doesNotContain("http://snomed.info/sct#417005");
}
@Test
public void validate_withProfileWithSlicingWithValidCode_returnsNoErrors() {
// setup
String profile = "http://example.ca/fhir/StructureDefinition/profile-procedure-with-slicing";
StructureDefinition profileProcedure = ClasspathUtil.loadResource(myFhirContext, StructureDefinition.class, "validation/procedure/profile-procedure-slicing.json");
myClient.update().resource(profileProcedure).execute();
final String statusCode = "completed";
final String procedureCode = "no-procedure-info";
final String statusSystem = "http://hl7.org/fhir/event-status";
final String snomedSystem = "http://snomed.info/sct";
final String absentUnknownSystem = "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips";
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json");
setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode, "validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json");
setupValueSetValidateCode("http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips", absentUnknownSystem, procedureCode, "validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json");
setupCodeSystemValidateCode(statusSystem, statusCode, "validation/procedure/validateCode-CodeSystem-event-status.json");
setupCodeSystemValidateCode(absentUnknownSystem, procedureCode, "validation/procedure/validateCode-CodeSystem-absent-or-unknown.json");
Procedure procedure = new Procedure();
procedure.setSubject(new Reference("Patient/P1"));
procedure.setStatus(Procedure.ProcedureStatus.fromCode(statusCode));
procedure.getCode().addCoding().setSystem(absentUnknownSystem).setCode(procedureCode);
procedure.getMeta().addProfile(profile);
// execute
List<String> errors = getValidationErrors(procedure);
assertThat(errors).hasSize(0);
}
private void setupValueSetValidateCode(String theUrl, String theSystem, String theCode, String theTerminologyResponseFile) {
ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl);
myCodeSystemProvider.addTerminologyResource(theSystem);
myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, ourCtx, theTerminologyResponseFile);
// 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);
// you will notice each of these calls require also a call to setupCodeSystemValidateCode
// that is necessary because VersionSpecificWorkerContextWrapper#validateCodeInValueSet
// which also attempts a validateCode against the CodeSystem after the validateCode against the ValueSet
}
private void setupCodeSystemValidateCode(String theUrl, String theCode, String theTerminologyResponseFile) {
CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl);
myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, ourCtx, theTerminologyResponseFile);
}
private List<String> getValidationErrors(IBaseResource theResource) {
MethodOutcome resultProcedure = myClient.validate().resource(theResource).execute();
OperationOutcome operationOutcome = (OperationOutcome) resultProcedure.getOperationOutcome();
return operationOutcome.getIssue().stream()
.filter(issue -> issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR)
.map(OperationOutcome.OperationOutcomeIssueComponent::getDiagnostics)
.toList();
}
}

View File

@ -0,0 +1,49 @@
{
"resourceType": "StructureDefinition",
"id": "profile-encounter",
"url": "http://example.ca/fhir/StructureDefinition/profile-encounter",
"version": "0.11.0",
"name": "EncounterProfile",
"title": "Encounter Profile",
"status": "active",
"date": "2022-10-15T12:00:00+00:00",
"publisher": "Example Organization",
"fhirVersion": "4.0.1",
"kind": "resource",
"abstract": false,
"type": "Encounter",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Encounter",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Encounter.identifier.type.coding",
"path": "Encounter.identifier.type.coding",
"min": 1,
"max": "1",
"mustSupport": true
},
{
"id": "Encounter.identifier.type.coding.system",
"path": "Encounter.identifier.type.coding.system",
"min": 1,
"fixedUri": "http://terminology.hl7.org/CodeSystem/v2-0203",
"mustSupport": true
},
{
"id": "Encounter.identifier.type.coding.code",
"path": "Encounter.identifier.type.coding.code",
"min": 1,
"fixedCode": "VN",
"mustSupport": true
},
{
"id": "Encounter.identifier.type.coding.display",
"path": "Encounter.identifier.type.coding.display",
"min": 1,
"fixedString": "Visit number",
"mustSupport": true
}
]
}
}

View File

@ -0,0 +1,59 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "planned"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/encounter-status"
},
{
"name": "version",
"valueString": "5.0.0-ballot"
},
{
"name": "display",
"valueString": "Planned"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
}
},
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to draft CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
}
}
]
}
}
]
}

View File

@ -0,0 +1,25 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "VN"
},
{
"name": "system",
"valueUri": "http://terminology.hl7.org/CodeSystem/v2-0203"
},
{
"name": "version",
"valueString": "3.0.0"
},
{
"name": "display",
"valueString": "Visit number"
}
]
}

View File

@ -0,0 +1,46 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "IMP"
},
{
"name": "system",
"valueUri": "http://terminology.hl7.org/CodeSystem/v3-ActCode"
},
{
"name": "version",
"valueString": "2018-08-12"
},
{
"name": "display",
"valueString": "inpatient encounter"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to draft CodeSystem http://terminology.hl7.org/CodeSystem/v3-ActCode|2018-08-12"
}
}
]
}
}
]
}

View File

@ -0,0 +1,59 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "planned"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/encounter-status"
},
{
"name": "version",
"valueString": "5.0.0-ballot"
},
{
"name": "display",
"valueString": "Planned"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
}
},
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to draft CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot"
}
}
]
}
}
]
}

View File

@ -0,0 +1,52 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": false
},
{
"name": "code",
"valueCode": "VN"
},
{
"name": "system",
"valueUri": "http://terminology.hl7.org/CodeSystem/v2-0203"
},
{
"name": "version",
"valueString": "3.0.0"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "not-in-vs"
}
],
"text": "The provided code 'http://terminology.hl7.org/CodeSystem/v2-0203#VN' was not found in the value set 'http://hl7.org/fhir/ValueSet/identifier-type|5.0.0-ballot'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}
},
{
"name": "message",
"valueString": "The provided code 'http://terminology.hl7.org/CodeSystem/v2-0203#VN' was not found in the value set 'http://hl7.org/fhir/ValueSet/identifier-type|5.0.0-ballot'"
}
]
}

View File

@ -0,0 +1,59 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "IMP"
},
{
"name": "system",
"valueUri": "http://terminology.hl7.org/CodeSystem/v3-ActCode"
},
{
"name": "version",
"valueString": "2018-08-12"
},
{
"name": "display",
"valueString": "inpatient encounter"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use ValueSet http://terminology.hl7.org/ValueSet/v3-ActEncounterCode|2014-03-26"
}
},
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to draft CodeSystem http://terminology.hl7.org/CodeSystem/v3-ActCode|2018-08-12"
}
}
]
}
}
]
}

View File

@ -0,0 +1,48 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": false
},
{
"name": "code",
"valueCode": "10xx"
},
{
"name": "system",
"valueUri": "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "invalid-code"
}
],
"text": "Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM' version '0.1.0'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}
},
{
"name": "message",
"valueString": "Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM' version '0.1.0'"
}
]
}

View File

@ -0,0 +1,25 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "final"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/observation-status"
},
{
"name": "version",
"valueString": "5.0.0-ballot"
},
{
"name": "display",
"valueString": "Final"
}
]
}

View File

@ -0,0 +1,48 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": false
},
{
"name": "code",
"valueCode": "10xx"
},
{
"name": "system",
"valueUri": "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "not-in-vs"
}
],
"text": "The provided code 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM#10xx' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-codes|5.0.0-ballot'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}
},
{
"name": "message",
"valueString": "The provided code 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM#10xx' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-codes|5.0.0-ballot'"
}
]
}

View File

@ -0,0 +1,25 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "final"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/observation-status"
},
{
"name": "version",
"valueString": "5.0.0-ballot"
},
{
"name": "display",
"valueString": "Final"
}
]
}

View File

@ -0,0 +1,79 @@
{
"resourceType": "StructureDefinition",
"id": "profile-procedure-with-slicing",
"url": "http://example.ca/fhir/StructureDefinition/profile-procedure-with-slicing",
"version": "0.11.0",
"name": "ProcedureProfile",
"title": "Procedure Profile",
"status": "active",
"date": "2022-10-15T12:00:00+00:00",
"publisher": "Example Organization",
"fhirVersion": "4.0.1",
"kind": "resource",
"abstract": false,
"type": "Procedure",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Procedure",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Procedure.code.coding",
"path": "Procedure.code.coding",
"slicing": {
"discriminator": [
{
"type": "pattern",
"path": "$this"
}
],
"description": "Discriminated by the bound value set",
"rules": "open"
},
"mustSupport": true,
"binding": {
"strength": "preferred",
"valueSet": "http://hl7.org/fhir/ValueSet/procedure-code"
}
},
{
"id": "Procedure.code.coding.display.extension:translation",
"path": "Procedure.code.coding.display.extension",
"sliceName": "translation"
},
{
"id": "Procedure.code.coding.display.extension:translation.extension",
"path": "Procedure.code.coding.display.extension.extension",
"min": 2
},
{
"id": "Procedure.code.coding:absentOrUnknownProcedure",
"path": "Procedure.code.coding",
"sliceName": "absentOrUnknownProcedure",
"short": "Optional slice for representing a code for absent problem or for unknown procedure",
"definition": "Code representing the statement \"absent problem\" or the statement \"procedures unknown\"",
"mustSupport": true,
"binding": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName",
"valueString": "absentOrUnknownProcedure"
}
],
"strength": "required",
"description": "A code to identify absent or unknown procedures",
"valueSet": "http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips"
}
},
{
"id": "Procedure.code.coding:absentOrUnknownProcedure.display.extension:translation",
"path": "Procedure.code.coding.display.extension",
"sliceName": "translation"
},
{
"id": "Procedure.code.coding:absentOrUnknownProcedure.display.extension:translation.extension",
"path": "Procedure.code.coding.display.extension.extension",
"min": 2
}
]
}
}

View File

@ -0,0 +1,50 @@
{
"resourceType": "StructureDefinition",
"id": "profile-procedure",
"url": "http://example.ca/fhir/StructureDefinition/profile-procedure",
"version": "0.11.0",
"name": "ProcedureProfile",
"title": "Procedure Profile",
"status": "active",
"date": "2022-10-15T12:00:00+00:00",
"publisher": "Example Organization",
"fhirVersion": "4.0.1",
"kind": "resource",
"abstract": false,
"type": "Procedure",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Procedure",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Procedure.code.coding",
"path": "Procedure.code.coding",
"slicing": {
"discriminator": [
{
"type": "pattern",
"path": "$this"
}
],
"description": "Discriminated by the bound value set",
"rules": "open"
},
"mustSupport": true,
"binding": {
"strength": "preferred",
"valueSet": "http://hl7.org/fhir/ValueSet/procedure-code"
}
},
{
"id": "Procedure.code.coding.display.extension:translation",
"path": "Procedure.code.coding.display.extension",
"sliceName": "translation"
},
{
"id": "Procedure.code.coding.display.extension:translation.extension",
"path": "Procedure.code.coding.display.extension.extension",
"min": 2
}
]
}
}

View File

@ -0,0 +1,46 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "no-procedure-info"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"
},
{
"name": "version",
"valueString": "1.1.0"
},
{
"name": "display",
"valueString": "No information about past history of procedures"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips|1.1.0"
}
}
]
}
}
]
}

View File

@ -0,0 +1,59 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "completed"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/event-status"
},
{
"name": "version",
"valueString": "5.0.0-ballot"
},
{
"name": "display",
"valueString": "Completed"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/event-status|5.0.0-ballot"
}
},
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to experimental CodeSystem http://hl7.org/fhir/event-status|5.0.0-ballot"
}
}
]
}
}
]
}

View File

@ -0,0 +1,48 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": false
},
{
"name": "code",
"valueCode": "xx417005"
},
{
"name": "system",
"valueUri": "http://snomed.info/sct"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "invalid-code"
}
],
"text": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}
},
{
"name": "message",
"valueString": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'"
}
]
}

View File

@ -0,0 +1,25 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "417005"
},
{
"name": "system",
"valueUri": "http://snomed.info/sct"
},
{
"name": "version",
"valueString": "http://snomed.info/sct/32506021000036107/version/20241031"
},
{
"name": "display",
"valueString": "Hospital re-admission"
}
]
}

View File

@ -0,0 +1,59 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "no-procedure-info"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"
},
{
"name": "version",
"valueString": "1.1.0"
},
{
"name": "display",
"valueString": "No information about past history of procedures"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use CodeSystem http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips|1.1.0"
}
},
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to trial-use ValueSet http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips|1.1.0"
}
}
]
}
}
]
}

View File

@ -0,0 +1,25 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "final"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/procedure-status"
},
{
"name": "version",
"valueString": "5.0.0-ballot"
},
{
"name": "display",
"valueString": "Final"
}
]
}

View File

@ -0,0 +1,48 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": false
},
{
"name": "code",
"valueCode": "no-procedure-info"
},
{
"name": "system",
"valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "not-in-vs"
}
],
"text": "The provided code 'http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips#no-procedure-info' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}
},
{
"name": "message",
"valueString": "The provided code 'http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips#no-procedure-info' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
}
]
}

View File

@ -0,0 +1,67 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": false
},
{
"name": "code",
"valueCode": "xx417005"
},
{
"name": "system",
"valueUri": "http://snomed.info/sct"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "not-in-vs"
}
],
"text": "The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
},
"location": [
"code"
],
"expression": [
"code"
]
},
{
"severity": "error",
"code": "code-invalid",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "invalid-code"
}
],
"text": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'"
},
"location": [
"code"
],
"expression": [
"code"
]
}
]
}
},
{
"name": "message",
"valueString": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'; The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'"
}
]
}

View File

@ -0,0 +1,59 @@
{
"resourceType": "Parameters",
"parameter": [
{
"name": "result",
"valueBoolean": true
},
{
"name": "code",
"valueCode": "417005"
},
{
"name": "system",
"valueUri": "http://snomed.info/sct"
},
{
"name": "version",
"valueString": "http://snomed.info/sct/32506021000036107/version/20241031"
},
{
"name": "display",
"valueString": "Hospital re-admission"
},
{
"name": "issues",
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to draft ValueSet http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot"
}
},
{
"severity": "information",
"code": "business-rule",
"details": {
"coding": [
{
"system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code": "status-check"
}
],
"text": "Reference to experimental ValueSet http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot"
}
}
]
}
}
]
}

View File

@ -81,7 +81,7 @@ public class CompositeInterceptorBroadcaster {
}
if (theRequestDetails != null && theRequestDetails.getInterceptorBroadcaster() != null && retVal) {
IInterceptorBroadcaster interceptorBroadcaster = theRequestDetails.getInterceptorBroadcaster();
interceptorBroadcaster.callHooks(thePointcut, theParams);
retVal = interceptorBroadcaster.callHooks(thePointcut, theParams);
}
return retVal;
}

View File

@ -0,0 +1,161 @@
package ca.uhn.fhir.rest.server.util;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CompositeInterceptorBroadcasterTest {
@Mock
private IInterceptorBroadcaster myModuleBroadcasterMock;
@Mock
private IInterceptorBroadcaster myReqDetailsBroadcasterMock;
@Mock
private Pointcut myPointcutMock;
@Mock
private HookParams myHookParamsMock;
@Mock
private RequestDetails myRequestDetailsMock;
@Test
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_RequestDetailsBroadcasterReturnsTrue_ThenReturnsTrue() {
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock,
myPointcutMock, myHookParamsMock);
assertThat(retVal).isTrue();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_RequestDetailsBroadcasterReturnsFalse_ThenReturnsFalse() {
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock,
myPointcutMock, myHookParamsMock);
assertThat(retVal).isFalse();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenModuleBroadcasterReturnsFalse_ThenSkipsBroadcasterInRequestDetails_And_ReturnsFalse() {
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock,
myPointcutMock, myHookParamsMock);
assertThat(retVal).isFalse();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
verify(myReqDetailsBroadcasterMock, never()).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_NullRequestDetailsBroadcaster_ThenReturnsTrue() {
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(null);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock, myPointcutMock,
myHookParamsMock);
assertThat(retVal).isTrue();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenModuleBroadcasterReturnsFalse_And_NullRequestDetailsBroadcaster_ThenReturnsFalse() {
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(null);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, myRequestDetailsMock, myPointcutMock,
myHookParamsMock);
assertThat(retVal).isFalse();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenModuleBroadcasterReturnsTrue_And_NullRequestDetails_ThenReturnsTrue() {
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, null, myPointcutMock, myHookParamsMock);
assertThat(retVal).isTrue();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenModuleBroadcasterReturnsFalse_And_NullRequestDetails_ThenReturnsFalse() {
when(myModuleBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(myModuleBroadcasterMock, null, myPointcutMock, myHookParamsMock);
assertThat(retVal).isFalse();
verify(myModuleBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenNullModuleBroadcaster_And_NullRequestDetails_ThenReturnsTrue() {
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(null, null, myPointcutMock, myHookParamsMock);
assertThat(retVal).isTrue();
}
@Test
void doCallHooks_WhenNullModuleBroadcaster_And_RequestDetailsBroadcasterReturnsTrue_ThenReturnsTrue() {
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(true);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(null, myRequestDetailsMock, myPointcutMock, myHookParamsMock);
assertThat(retVal).isTrue();
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
@Test
void doCallHooks_WhenNullModuleBroadcaster_And_RequestDetailsBroadcasterReturnsFalse_ThenReturnsFalse() {
when(myRequestDetailsMock.getInterceptorBroadcaster()).thenReturn(myReqDetailsBroadcasterMock);
when(myReqDetailsBroadcasterMock.callHooks(myPointcutMock, myHookParamsMock)).thenReturn(false);
boolean retVal = CompositeInterceptorBroadcaster.doCallHooks(null, myRequestDetailsMock, myPointcutMock, myHookParamsMock);
assertThat(retVal).isFalse();
verify(myReqDetailsBroadcasterMock).callHooks(myPointcutMock, myHookParamsMock);
}
}

View File

@ -0,0 +1,123 @@
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.test.utilities.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
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.util.ClasspathUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IDomainResource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public interface IValidationProviders {
String CODE_SYSTEM = "http://code.system/url";
String CODE_SYSTEM_VERSION = "1.0.0";
String CODE_SYSTEM_NAME = "Test Code System";
String CODE = "CODE";
String VALUE_SET_URL = "http://value.set/url";
String DISPLAY = "Explanation for code TestCode.";
String LANGUAGE = "en";
String ERROR_MESSAGE = "This is an error message";
interface IMyValidationProvider extends IResourceProvider {
void addException(String theOperation, String theUrl, String theCode, Exception theException);
<P extends IBaseParameters> void addTerminologyResponse(String theOperation, String theUrl, String theCode, P theReturnParams);
IBaseParameters addTerminologyResponse(String theOperation, String theUrl, String theCode, FhirContext theFhirContext, String theTerminologyResponseFile);
}
abstract class MyValidationProvider<T extends IDomainResource> implements IMyValidationProvider {
private final Map<String, Exception> myExceptionMap = new HashMap<>();
private boolean myShouldThrowExceptionForResourceNotFound = true;
private final Map<String, IBaseParameters> myTerminologyResponseMap = new HashMap<>();
private final Map<String, T> myTerminologyResourceMap = new HashMap<>();
static String getInputKey(String theOperation, String theUrl, String theCode) {
return theOperation + "-" + theUrl + "#" + theCode;
}
public void setShouldThrowExceptionForResourceNotFound(boolean theShouldThrowExceptionForResourceNotFound) {
myShouldThrowExceptionForResourceNotFound = theShouldThrowExceptionForResourceNotFound;
}
public void addException(String theOperation, String theUrl, String theCode, Exception theException) {
String inputKey = getInputKey(theOperation, theUrl, theCode);
myExceptionMap.put(inputKey, theException);
}
abstract Class<? extends IBaseParameters> getParameterType();
@Override
public <P extends IBaseParameters> void addTerminologyResponse(String theOperation, String theUrl, String theCode, P theReturnParams) {
myTerminologyResponseMap.put(getInputKey(theOperation, theUrl, theCode), theReturnParams);
}
public IBaseParameters addTerminologyResponse(String theOperation, String theUrl, String theCode, FhirContext theFhirContext, String theTerminologyResponseFile) {
IBaseParameters responseParams = ClasspathUtil.loadResource(theFhirContext, getParameterType(), theTerminologyResponseFile);
addTerminologyResponse(theOperation, theUrl, theCode, responseParams);
return responseParams;
}
protected void addTerminologyResource(String theUrl, T theResource) {
myTerminologyResourceMap.put(theUrl, theResource);
}
public abstract T addTerminologyResource(String theUrl);
protected IBaseParameters getTerminologyResponse(String theOperation, String theUrl, String theCode) throws Exception {
String inputKey = getInputKey(theOperation, theUrl, theCode);
if (myExceptionMap.containsKey(inputKey)) {
throw myExceptionMap.get(inputKey);
}
IBaseParameters params = myTerminologyResponseMap.get(inputKey);
if (params == null) {
throw new IllegalStateException("Test setup incomplete. Missing return params for " + inputKey);
}
return params;
}
protected T getTerminologyResource(UriParam theUrlParam) {
if (theUrlParam.isEmpty()) {
throw new IllegalStateException("CodeSystem url should not be null.");
}
String urlValue = theUrlParam.getValue();
if (!myTerminologyResourceMap.containsKey(urlValue) && myShouldThrowExceptionForResourceNotFound) {
throw new IllegalStateException("Test setup incomplete. CodeSystem not found " + urlValue);
}
return myTerminologyResourceMap.get(urlValue);
}
@Search
public List<T> find(@RequiredParam(name = "url") UriParam theUrlParam) {
T resource = getTerminologyResource(theUrlParam);
return resource != null ? List.of(resource) : List.of();
}
}
interface IMyLookupCodeProvider extends IResourceProvider {
void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult);
}
}

View File

@ -0,0 +1,137 @@
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.test.utilities.validation;
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.api.server.RequestDetails;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
public interface IValidationProvidersDstu3 {
@SuppressWarnings("unused")
class MyCodeSystemProviderDstu3 extends IValidationProviders.MyValidationProvider<CodeSystem> {
@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 IBaseParameters 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
) throws Exception {
String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$validate-code", url, code);
}
@Operation(name = "$lookup", idempotent = true, returnParameters= {
@OperationParam(name = "name", type = StringType.class, min = 1),
@OperationParam(name = "version", type = StringType.class),
@OperationParam(name = "display", type = StringType.class, min = 1),
@OperationParam(name = "abstract", type = BooleanType.class, min = 1),
@OperationParam(name = "property", type = StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
})
public IBaseParameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name = "code", max = 1) CodeType theCode,
@OperationParam(name = "system",max = 1) UriType theSystem,
@OperationParam(name = "coding", max = 1) Coding theCoding,
@OperationParam(name = "version", max = 1) StringType theVersion,
@OperationParam(name = "displayLanguage", max = 1) CodeType theDisplayLanguage,
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
RequestDetails theRequestDetails
) throws Exception {
String url = theSystem != null ? theSystem.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$lookup", url, code);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
@Override
Class<Parameters> getParameterType() {
return Parameters.class;
}
@Override
public CodeSystem addTerminologyResource(String theUrl) {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
codeSystem.setUrl(theUrl);
addTerminologyResource(theUrl, codeSystem);
return codeSystem;
}
}
@SuppressWarnings("unused")
class MyValueSetProviderDstu3 extends IValidationProviders.MyValidationProvider<ValueSet> {
@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 IBaseParameters 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
) throws Exception {
String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$validate-code", url, code);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
@Override
Class<Parameters> getParameterType() {
return Parameters.class;
}
@Override
public ValueSet addTerminologyResource(String theUrl) {
ValueSet valueSet = new ValueSet();
valueSet.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
valueSet.setUrl(theUrl);
addTerminologyResource(theUrl, valueSet);
return valueSet;
}
}
}

View File

@ -1,12 +1,29 @@
package org.hl7.fhir.r4.validation;
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.test.utilities.validation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
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.api.server.RequestDetails;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
@ -21,38 +38,29 @@ import org.hl7.fhir.r4.model.ValueSet;
import java.util.List;
public interface IValidateCodeProvidersR4 {
public interface IValidationProvidersR4 {
@SuppressWarnings("unused")
class MyCodeSystemProviderR4 implements IValidationProviders.IMyCodeSystemProvider {
private UriType mySystemUrl;
private CodeType myCode;
private StringType myDisplay;
private Exception myException;
private Parameters myReturnParams;
class MyCodeSystemProviderR4 extends IValidationProviders.MyValidationProvider<CodeSystem> {
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@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(
public IBaseParameters 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
) throws Exception {
mySystemUrl = theCodeSystemUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$validate-code", url, code);
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@Operation(name = "$lookup", idempotent = true, returnParameters= {
@OperationParam(name = "name", type = StringType.class, min = 1),
@OperationParam(name = "version", type = StringType.class),
@OperationParam(name = "display", type = StringType.class, min = 1),
@ -69,54 +77,39 @@ public interface IValidateCodeProvidersR4 {
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
RequestDetails theRequestDetails
) throws Exception {
mySystemUrl = theSystem;
myCode = theCode;
if (myException != null) {
throw myException;
}
return myReturnParams;
String url = theSystem != null ? theSystem.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$lookup", url, code);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
Class<Parameters> getParameterType() {
return Parameters.class;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
public CodeSystem addTerminologyResource(String theUrl) {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
codeSystem.setUrl(theUrl);
addTerminologyResource(theUrl, codeSystem);
return codeSystem;
}
}
@SuppressWarnings("unused")
class MyValueSetProviderR4 implements IValidationProviders.IMyValueSetProvider {
private Exception myException;
private Parameters myReturnParams;
private UriType mySystemUrl;
private UriType myValueSetUrl;
private CodeType myCode;
private StringType myDisplay;
class MyValueSetProviderR4 extends IValidationProviders.MyValidationProvider<ValueSet> {
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@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(
public IBaseParameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@ -125,41 +118,25 @@ public interface IValidateCodeProvidersR4 {
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) throws Exception {
mySystemUrl = theSystem;
myValueSetUrl = theValueSetUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null;
String code = theCode != null ? theCode.getValue() : null;
return getTerminologyResponse("$validate-code", url, code);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
public void setException(Exception theException) {
myException = theException;
@Override
Class<Parameters> getParameterType() {
return Parameters.class;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
@Override
public String getValueSet() {
return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
public ValueSet addTerminologyResource(String theUrl) {
ValueSet valueSet = new ValueSet();
valueSet.setId(theUrl.substring(0, theUrl.lastIndexOf("/")));
valueSet.setUrl(theUrl);
addTerminologyResource(theUrl, valueSet);
return valueSet;
}
}
}

View File

@ -190,7 +190,7 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage(theMessage)
.setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue(
.setIssues(Collections.singletonList(new CodeValidationIssue(
theMessage,
IssueSeverity.ERROR,
CodeValidationIssueCode.INVALID,

View File

@ -28,7 +28,6 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import java.util.ArrayList;
import java.util.Collections;
@ -258,7 +257,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode);
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
CodeValidationResult codeValidationResult = new CodeValidationResult();
codeValidationResult.setSeverityCode("error");
codeValidationResult.setSeverity(IssueSeverity.ERROR);
String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code "
+ theCodeSystemUrlAndVersion + "#" + theCode;
@ -267,7 +266,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
codeValidationResult.setMessage(msg);
codeValidationResult.addCodeValidationIssue(e.getCodeValidationIssue());
codeValidationResult.addIssue(e.getCodeValidationIssue());
return codeValidationResult;
}
@ -551,18 +550,18 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
if (valueSetResult != null) {
codeValidationResult = valueSetResult;
} else {
ValidationMessage.IssueSeverity severity;
IValidationSupport.IssueSeverity severity;
String message;
CodeValidationIssueCode issueCode = CodeValidationIssueCode.CODE_INVALID;
CodeValidationIssueCoding issueCoding = CodeValidationIssueCoding.INVALID_CODE;
if ("fragment".equals(codeSystemResourceContentMode)) {
severity = ValidationMessage.IssueSeverity.WARNING;
severity = IValidationSupport.IssueSeverity.WARNING;
message = "Unknown code in fragment CodeSystem '"
+ getFormattedCodeSystemAndCodeForMessage(
theCodeSystemUrlAndVersionToValidate, theCodeToValidate)
+ "'";
} else {
severity = ValidationMessage.IssueSeverity.ERROR;
severity = IValidationSupport.IssueSeverity.ERROR;
message = "Unknown code '"
+ getFormattedCodeSystemAndCodeForMessage(
theCodeSystemUrlAndVersionToValidate, theCodeToValidate)
@ -574,10 +573,9 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
codeValidationResult = new CodeValidationResult()
.setSeverityCode(severity.toCode())
.setSeverity(severity)
.setMessage(message)
.addCodeValidationIssue(new CodeValidationIssue(
message, getIssueSeverityFromCodeValidationIssue(severity), issueCode, issueCoding));
.addIssue(new CodeValidationIssue(message, severity, issueCode, issueCoding));
}
return codeValidationResult;
@ -589,19 +587,6 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
+ theCodeToValidate;
}
private IValidationSupport.IssueSeverity getIssueSeverityFromCodeValidationIssue(
ValidationMessage.IssueSeverity theSeverity) {
switch (theSeverity) {
case ERROR:
return IValidationSupport.IssueSeverity.ERROR;
case WARNING:
return IValidationSupport.IssueSeverity.WARNING;
case INFORMATION:
return IValidationSupport.IssueSeverity.INFORMATION;
}
return null;
}
private CodeValidationResult findCodeInExpansion(
String theCodeToValidate,
String theDisplayToValidate,
@ -1123,8 +1108,8 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
new CodeValidationIssue(
theMessage,
IssueSeverity.ERROR,
CodeValidationIssueCode.OTHER,
CodeValidationIssueCoding.OTHER));
CodeValidationIssueCode.INVALID,
CodeValidationIssueCoding.VS_INVALID));
}
for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next :
subExpansion.getExpansion().getContains()) {
@ -1376,7 +1361,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
.setCodeSystemVersion(theCodeSystemVersion)
.setDisplay(theExpectedDisplay);
if (issueSeverity != null) {
codeValidationResult.setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue(
codeValidationResult.setIssues(Collections.singletonList(new CodeValidationIssue(
message,
theIssueSeverityForCodeDisplayMismatch,
CodeValidationIssueCode.INVALID,

View File

@ -28,6 +28,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
@ -631,7 +632,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return new CodeValidationResult()
.setSeverity(severity)
.setMessage(theMessage)
.addCodeValidationIssue(new CodeValidationIssue(
.addIssue(new CodeValidationIssue(
theMessage, severity, theIssueCode, CodeValidationIssueCoding.INVALID_CODE));
}
@ -680,13 +681,13 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
createCodeValidationIssues(
(IBaseOperationOutcome) issuesValue.get(),
fhirContext.getVersion().getVersion())
.ifPresent(i -> i.forEach(result::addCodeValidationIssue));
.ifPresent(i -> i.forEach(result::addIssue));
} else {
// create a validation issue out of the message
// this is a workaround to overcome an issue in the FHIR Validator library
// where ValueSet bindings are only reading issues but not messages
// @see https://github.com/hapifhir/org.hl7.fhir.core/issues/1766
result.addCodeValidationIssue(createCodeValidationIssue(result.getMessage()));
result.addIssue(createCodeValidationIssue(result.getMessage()));
}
return result;
}
@ -717,23 +718,42 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
private static Collection<CodeValidationIssue> createCodeValidationIssuesR4(OperationOutcome theOperationOutcome) {
return theOperationOutcome.getIssue().stream()
.map(issueComponent ->
createCodeValidationIssue(issueComponent.getDetails().getText()))
.map(issueComponent -> {
String diagnostics = issueComponent.getDiagnostics();
IssueSeverity issueSeverity =
IssueSeverity.fromCode(issueComponent.getSeverity().toCode());
String issueTypeCode = issueComponent.getCode().toCode();
CodeableConcept details = issueComponent.getDetails();
CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode);
CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText());
details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode()));
issue.setDetails(issueDetails);
return issue;
})
.collect(Collectors.toList());
}
private static Collection<CodeValidationIssue> createCodeValidationIssuesDstu3(
org.hl7.fhir.dstu3.model.OperationOutcome theOperationOutcome) {
return theOperationOutcome.getIssue().stream()
.map(issueComponent ->
createCodeValidationIssue(issueComponent.getDetails().getText()))
.map(issueComponent -> {
String diagnostics = issueComponent.getDiagnostics();
IssueSeverity issueSeverity =
IssueSeverity.fromCode(issueComponent.getSeverity().toCode());
String issueTypeCode = issueComponent.getCode().toCode();
org.hl7.fhir.dstu3.model.CodeableConcept details = issueComponent.getDetails();
CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode);
CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText());
details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode()));
issue.setDetails(issueDetails);
return issue;
})
.collect(Collectors.toList());
}
private static CodeValidationIssue createCodeValidationIssue(String theMessage) {
return new CodeValidationIssue(
theMessage,
// assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match
IssueSeverity.ERROR,
CodeValidationIssueCode.INVALID,
CodeValidationIssueCoding.INVALID_CODE);

View File

@ -87,7 +87,7 @@ public class UnknownCodeSystemWarningValidationSupport extends BaseValidationSup
result.setSeverity(null);
result.setMessage(null);
} else {
result.addCodeValidationIssue(new CodeValidationIssue(
result.addIssue(new CodeValidationIssue(
theMessage,
myNonExistentCodeSystemSeverity,
CodeValidationIssueCode.NOT_FOUND,

View File

@ -11,6 +11,17 @@ public final class ValidationSupportUtils {
private ValidationSupportUtils() {}
/**
* This method extracts a code system that can be (potentially) associated with a code when
* performing validation against a ValueSet. This method was created for internal purposes.
* Please use this method with care because it will only cover some
* use-cases (e.g. standard bindings) while for others it may not return correct results or return null.
* An incorrect result could be considered if the resource declares a code with a system, and you're calling
* this method to check a binding against a ValueSet that has nothing to do with that system.
* @param theValueSet the valueSet
* @param theCode the code
* @return the system which can be associated with the code
*/
public static String extractCodeSystemForCode(IBaseResource theValueSet, String theCode) {
if (theValueSet instanceof org.hl7.fhir.dstu3.model.ValueSet) {
return extractCodeSystemForCodeDSTU3((org.hl7.fhir.dstu3.model.ValueSet) theValueSet, theCode);

View File

@ -62,6 +62,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -296,7 +297,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
theResult.getCodeSystemVersion(),
conceptDefinitionComponent,
display,
getIssuesForCodeValidation(theResult.getCodeValidationIssues()));
getIssuesForCodeValidation(theResult.getIssues()));
}
if (retVal == null) {
@ -307,73 +308,36 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
}
private List<OperationOutcome.OperationOutcomeIssueComponent> getIssuesForCodeValidation(
List<IValidationSupport.CodeValidationIssue> codeValidationIssues) {
List<OperationOutcome.OperationOutcomeIssueComponent> issues = new ArrayList<>();
List<IValidationSupport.CodeValidationIssue> theIssues) {
List<OperationOutcome.OperationOutcomeIssueComponent> issueComponents = new ArrayList<>();
for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeValidationIssues) {
for (IValidationSupport.CodeValidationIssue issue : theIssues) {
OperationOutcome.IssueSeverity severity =
OperationOutcome.IssueSeverity.fromCode(issue.getSeverity().getCode());
OperationOutcome.IssueType issueType =
OperationOutcome.IssueType.fromCode(issue.getType().getCode());
String diagnostics = issue.getDiagnostics();
CodeableConcept codeableConcept = new CodeableConcept().setText(codeValidationIssue.getMessage());
codeableConcept.addCoding(
"http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
getIssueCodingFromCodeValidationIssue(codeValidationIssue),
null);
IValidationSupport.CodeValidationIssueDetails details = issue.getDetails();
CodeableConcept codeableConcept = new CodeableConcept().setText(details.getText());
details.getCodings().forEach(detailCoding -> codeableConcept
.addCoding()
.setSystem(detailCoding.getSystem())
.setCode(detailCoding.getCode()));
OperationOutcome.OperationOutcomeIssueComponent issue =
OperationOutcome.OperationOutcomeIssueComponent issueComponent =
new OperationOutcome.OperationOutcomeIssueComponent()
.setSeverity(getIssueSeverityFromCodeValidationIssue(codeValidationIssue))
.setCode(getIssueTypeFromCodeValidationIssue(codeValidationIssue))
.setDetails(codeableConcept);
issue.getDetails().setText(codeValidationIssue.getMessage());
issue.addExtension()
.setSeverity(severity)
.setCode(issueType)
.setDetails(codeableConcept)
.setDiagnostics(diagnostics);
issueComponent
.addExtension()
.setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id")
.setValue(new StringType("Terminology_PassThrough_TX_Message"));
issues.add(issue);
issueComponents.add(issueComponent);
}
return issues;
}
private String getIssueCodingFromCodeValidationIssue(IValidationSupport.CodeValidationIssue codeValidationIssue) {
switch (codeValidationIssue.getCoding()) {
case VS_INVALID:
return "vs-invalid";
case NOT_FOUND:
return "not-found";
case NOT_IN_VS:
return "not-in-vs";
case INVALID_CODE:
return "invalid-code";
case INVALID_DISPLAY:
return "invalid-display";
}
return null;
}
private OperationOutcome.IssueType getIssueTypeFromCodeValidationIssue(
IValidationSupport.CodeValidationIssue codeValidationIssue) {
switch (codeValidationIssue.getCode()) {
case NOT_FOUND:
return OperationOutcome.IssueType.NOTFOUND;
case CODE_INVALID:
return OperationOutcome.IssueType.CODEINVALID;
case INVALID:
return OperationOutcome.IssueType.INVALID;
}
return null;
}
private OperationOutcome.IssueSeverity getIssueSeverityFromCodeValidationIssue(
IValidationSupport.CodeValidationIssue codeValidationIssue) {
switch (codeValidationIssue.getSeverity()) {
case FATAL:
return OperationOutcome.IssueSeverity.FATAL;
case ERROR:
return OperationOutcome.IssueSeverity.ERROR;
case WARNING:
return OperationOutcome.IssueSeverity.WARNING;
case INFORMATION:
return OperationOutcome.IssueSeverity.INFORMATION;
}
return null;
return issueComponents;
}
@Override
@ -851,25 +815,22 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
.getRootValidationSupport()
.validateCodeInValueSet(
myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet);
if (result != null) {
if (result != null && theSystem != null) {
/* We got a value set result, which could be successful, or could contain errors/warnings. The code
might also be invalid in the code system, so we will check that as well and add those issues
to our result.
*/
IValidationSupport.CodeValidationResult codeSystemResult =
validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay);
final boolean valueSetResultContainsInvalidDisplay = result.getCodeValidationIssues().stream()
.anyMatch(codeValidationIssue -> codeValidationIssue.getCoding()
== IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY);
final boolean valueSetResultContainsInvalidDisplay = result.getIssues().stream()
.anyMatch(VersionSpecificWorkerContextWrapper::hasInvalidDisplayDetailCode);
if (codeSystemResult != null) {
for (IValidationSupport.CodeValidationIssue codeValidationIssue :
codeSystemResult.getCodeValidationIssues()) {
for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeSystemResult.getIssues()) {
/* Value set validation should already have checked the display name. If we get INVALID_DISPLAY
issues from code system validation, they will only repeat what was already caught.
*/
if (codeValidationIssue.getCoding() != IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY
|| !valueSetResultContainsInvalidDisplay) {
result.addCodeValidationIssue(codeValidationIssue);
if (!hasInvalidDisplayDetailCode(codeValidationIssue) || !valueSetResultContainsInvalidDisplay) {
result.addIssue(codeValidationIssue);
}
}
}
@ -877,6 +838,10 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
return result;
}
private static boolean hasInvalidDisplayDetailCode(IValidationSupport.CodeValidationIssue theIssue) {
return theIssue.hasIssueDetailCode(INVALID_DISPLAY.getCode());
}
private IValidationSupport.CodeValidationResult validateCodeInCodeSystem(
ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) {
return myValidationSupportContext

View File

@ -9,6 +9,7 @@ import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
import ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.junit.jupiter.api.Test;
@ -21,12 +22,12 @@ 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.IValidationProviders.CODE;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_NAME;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.LANGUAGE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_NAME;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.LANGUAGE;
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;
@ -189,8 +190,6 @@ public interface ILookupCodeTest {
// verify
assertNotNull(outcome);
assertEquals(theRequest.getCode(), getLookupCodeProvider().getCode());
assertEquals(theRequest.getSystem(), getLookupCodeProvider().getSystem());
assertEquals(theExpectedResult.isFound(), outcome.isFound());
assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage());
assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName());

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.junit.jupiter.api.Test;

View File

@ -1,17 +1,76 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createCodeValidationIssues;
public interface IRemoteTerminologyValidateCodeTest extends IValidateCodeTest {
default List<IValidationSupport.CodeValidationIssue> getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) {
// this method should be removed once support for issues is fully implemented across all validator types
Optional<Collection<IValidationSupport.CodeValidationIssue>> issues = RemoteTerminologyServiceValidationSupport.createCodeValidationIssues(theOperationOutcome, getService().getFhirContext().getVersion().getVersion());
return issues.map(theCodeValidationIssues -> theCodeValidationIssues.stream().toList()).orElseGet(List::of);
}
@Test
default void createCodeValidationIssues_withCodeSystemOutcomeForInvalidCode_returnsAsExpected() {
IBaseOperationOutcome outcome = getCodeSystemInvalidCodeOutcome();
FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion();
Optional<Collection<IValidationSupport.CodeValidationIssue>> issuesOptional = createCodeValidationIssues(outcome, versionEnum);
assertThat(issuesOptional).isPresent();
assertThat(issuesOptional.get()).hasSize(1);
IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next();
assertThat(issue.getType().getCode()).isEqualTo("code-invalid");
assertThat(issue.getSeverity().getCode()).isEqualTo("error");
assertThat(issue.getDetails().getCodings()).hasSize(1);
IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0);
assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type");
assertThat(issueCoding.getCode()).isEqualTo("invalid-code");
assertThat(issue.getDetails().getText()).isEqualTo("Unknown code 'CODE' in the CodeSystem 'http://code.system/url' version '1.0.0'");
assertThat(issue.getDiagnostics()).isNull();
}
@Test
default void createCodeValidationIssues_withValueSetOutcomeForInvalidCode_returnsAsExpected() {
IBaseOperationOutcome outcome = getValueSetInvalidCodeOutcome();
FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion();
Optional<Collection<IValidationSupport.CodeValidationIssue>> issuesOptional = createCodeValidationIssues(outcome, versionEnum);
assertThat(issuesOptional).isPresent();
assertThat(issuesOptional.get()).hasSize(2);
IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next();
assertThat(issue.getType().getCode()).isEqualTo("code-invalid");
assertThat(issue.getSeverity().getCode()).isEqualTo("error");
assertThat(issue.getDetails().getCodings()).hasSize(1);
IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0);
assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type");
assertThat(issueCoding.getCode()).isEqualTo("not-in-vs");
assertThat(issue.getDetails().getText()).isEqualTo("The provided code 'http://code.system/url#CODE' was not found in the value set 'http://value.set/url%7C1.0.0'");
assertThat(issue.getDiagnostics()).isNull();
}
@Test
default void createCodeValidationIssues_withValueSetOutcomeWithCustomDetailCode_returnsAsExpected() {
IBaseOperationOutcome outcome = getValueSetCustomDetailCodeOutcome();
FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion();
Optional<Collection<IValidationSupport.CodeValidationIssue>> issuesOptional = createCodeValidationIssues(outcome, versionEnum);
assertThat(issuesOptional).isPresent();
assertThat(issuesOptional.get()).hasSize(1);
IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next();
assertThat(issue.getType().getCode()).isEqualTo("processing");
assertThat(issue.getSeverity().getCode()).isEqualTo("information");
assertThat(issue.getDetails().getCodings()).hasSize(1);
IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0);
assertThat(issueCoding.getSystem()).isEqualTo("http://example.com/custom-issue-type");
assertThat(issueCoding.getCode()).isEqualTo("valueset-is-draft");
assertThat(issue.getDetails().getText()).isNull();
assertThat(issue.getDiagnostics()).isEqualTo("The ValueSet status is marked as draft.");
}
}

View File

@ -4,6 +4,9 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -16,12 +19,13 @@ import java.util.List;
import java.util.stream.Stream;
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.ERROR_MESSAGE;
import static org.hl7.fhir.common.hapi.validation.IValidationProviders.VALUE_SET_URL;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -31,21 +35,35 @@ import static org.junit.jupiter.api.Assertions.fail;
public interface IValidateCodeTest {
IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider();
IValidationProviders.IMyValueSetProvider getValueSetProvider();
IValidationProviders.IMyValidationProvider getCodeSystemProvider();
IValidationProviders.IMyValidationProvider getValueSetProvider();
IValidationSupport getService();
IBaseParameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource);
String getCodeSystemError();
String getValueSetError();
IBaseOperationOutcome getCodeSystemInvalidCodeOutcome();
IBaseOperationOutcome getValueSetInvalidCodeOutcome();
IBaseOperationOutcome getValueSetCustomDetailCodeOutcome();
default IBaseOperationOutcome getCodeSystemInvalidCodeOutcome(Class<? extends IBaseOperationOutcome> theResourceClass) {
return getOutcome(theResourceClass, "/terminology/OperationOutcome-CodeSystem-invalid-code.json");
}
default IBaseOperationOutcome getValueSetInvalidCodeOutcome(Class<? extends IBaseOperationOutcome> theResourceClass) {
return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-invalid-code.json");
}
default IBaseOperationOutcome getValueSetCustomDetailCodeOutcome(Class<? extends IBaseOperationOutcome> theResourceClass) {
return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-custom-issue-detail.json");
}
default IBaseOperationOutcome getOutcome(Class<? extends IBaseOperationOutcome> theResourceClass, String theFile) {
return ClasspathUtil.loadResource(getService().getFhirContext(), theResourceClass, theFile);
}
default void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
getCodeSystemProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
getCodeSystemProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, CODE_SYSTEM, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
default void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
getValueSetProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
getValueSetProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
@Test
@ -91,8 +109,8 @@ public interface IValidateCodeTest {
String theValidationMessage,
String theCodeSystem,
String theValueSetUrl) {
getCodeSystemProvider().setException(theException);
getValueSetProvider().setException(theException);
getCodeSystemProvider().addException(OPERATION_VALIDATE_CODE, theCodeSystem, CODE, theException);
getValueSetProvider().addException(OPERATION_VALIDATE_CODE, theValueSetUrl, CODE, theException);
CodeValidationResult outcome = getService().validateCode(null, null, theCodeSystem, CODE, DISPLAY, theValueSetUrl);
verifyErrorResultFromException(outcome, theValidationMessage, theServerMessage);
@ -105,7 +123,7 @@ public interface IValidateCodeTest {
for (String message : theMessages) {
assertTrue(outcome.getMessage().contains(message));
}
assertFalse(outcome.getCodeValidationIssues().isEmpty());
assertFalse(outcome.getIssues().isEmpty());
}
@Test
@ -130,11 +148,7 @@ public interface IValidateCodeTest {
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
assertTrue(outcome.getIssues().isEmpty());
}
@Test
@ -147,9 +161,7 @@ public interface IValidateCodeTest {
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getCodeSystemProvider().getCode());
assertTrue(outcome.getIssues().isEmpty());
}
@Test
@ -165,10 +177,7 @@ public interface IValidateCodeTest {
assertNull(outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getCodeSystemProvider().getCode());
assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem());
assertTrue(outcome.getIssues().isEmpty());
}
@Test
@ -184,15 +193,11 @@ public interface IValidateCodeTest {
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getCodeSystemProvider().getCode());
assertEquals(DISPLAY, getCodeSystemProvider().getDisplay());
assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem());
assertTrue(outcome.getIssues().isEmpty());
}
@Test
default void validateCode_withCodeSystemError_returnsCorrectly() {
default void validateCode_withCodeSystemErrorWithDiagnosticsWithIssues_returnsCorrectly() {
IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome();
createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, invalidCodeOutcome);
@ -204,12 +209,12 @@ public interface IValidateCodeTest {
// assertEquals(CODE, outcome.getCode());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(getCodeSystemError(), outcome.getMessage());
assertFalse(outcome.getCodeValidationIssues().isEmpty());
assertFalse(outcome.getIssues().isEmpty());
verifyIssues(invalidCodeOutcome, outcome);
}
@Test
default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() {
default void validateCode_withCodeSystemErrorWithDiagnosticsWithoutIssues_returnsCorrectly() {
createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, null);
CodeValidationResult outcome = getService()
@ -223,10 +228,32 @@ public interface IValidateCodeTest {
assertNull(outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(expectedError, outcome.getMessage());
assertFalse(outcome.getCodeValidationIssues().isEmpty());
assertEquals(1, outcome.getCodeValidationIssues().size());
assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage());
assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity());
assertFalse(outcome.getIssues().isEmpty());
assertEquals(1, outcome.getIssues().size());
assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics());
assertEquals(ERROR, outcome.getIssues().get(0).getSeverity());
}
@Test
default void validateCode_withCodeSystemErrorWithoutDiagnosticsWithIssues_returnsCorrectly() {
IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome();
createCodeSystemReturnParameters(false, null, null, invalidCodeOutcome);
CodeValidationResult outcome = getService()
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
String expectedError = getCodeSystemError();
assertNotNull(outcome);
assertEquals(CODE_SYSTEM, outcome.getCodeSystemName());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
// assertEquals(CODE, outcome.getCode());
assertNull(outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertNull(outcome.getMessage());
assertFalse(outcome.getIssues().isEmpty());
assertEquals(1, outcome.getIssues().size());
assertNull(outcome.getIssues().get(0).getDiagnostics());
assertEquals(ERROR, outcome.getIssues().get(0).getSeverity());
}
@Test
@ -242,10 +269,7 @@ public interface IValidateCodeTest {
assertNull(outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
assertTrue(outcome.getIssues().isEmpty());
}
@Test
@ -261,11 +285,7 @@ public interface IValidateCodeTest {
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertTrue(outcome.getCodeValidationIssues().isEmpty());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
assertTrue(outcome.getIssues().isEmpty());
}
@Test
@ -283,13 +303,9 @@ public interface IValidateCodeTest {
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(expectedError, outcome.getMessage());
assertEquals(1, outcome.getCodeValidationIssues().size());
assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage());
assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity());
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
assertEquals(1, outcome.getIssues().size());
assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics());
assertEquals(ERROR, outcome.getIssues().get(0).getSeverity());
}
@Test
@ -306,24 +322,28 @@ public interface IValidateCodeTest {
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(ERROR, outcome.getSeverity());
assertEquals(getValueSetError(), outcome.getMessage());
assertFalse(outcome.getCodeValidationIssues().isEmpty());
assertFalse(outcome.getIssues().isEmpty());
verifyIssues(invalidCodeOutcome, outcome);
assertEquals(CODE, getValueSetProvider().getCode());
assertEquals(DISPLAY, getValueSetProvider().getDisplay());
assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet());
}
default void verifyIssues(IBaseOperationOutcome theOperationOutcome, CodeValidationResult theResult) {
List<IValidationSupport.CodeValidationIssue> issues = getCodeValidationIssues(theOperationOutcome);
assertEquals(issues.size(), theResult.getCodeValidationIssues().size());
assertEquals(issues.size(), theResult.getIssues().size());
for (int i = 0; i < issues.size(); i++) {
IValidationSupport.CodeValidationIssue expectedIssue = issues.get(i);
IValidationSupport.CodeValidationIssue actualIssue = theResult.getCodeValidationIssues().get(i);
assertEquals(expectedIssue.getCode(), actualIssue.getCode());
IValidationSupport.CodeValidationIssue actualIssue = theResult.getIssues().get(i);
assertEquals(expectedIssue.getType().getCode(), actualIssue.getType().getCode());
assertEquals(expectedIssue.getSeverity(), actualIssue.getSeverity());
assertEquals(expectedIssue.getCoding(), actualIssue.getCoding());
assertEquals(expectedIssue.getMessage(), actualIssue.getMessage());
assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText());
assertEquals(expectedIssue.getDetails().getCodings().size(), actualIssue.getDetails().getCodings().size());
for (int index = 0; index < expectedIssue.getDetails().getCodings().size(); index++) {
IValidationSupport.CodeValidationIssueCoding expectedCoding = expectedIssue.getDetails().getCodings().get(index);
IValidationSupport.CodeValidationIssueCoding actualCoding = actualIssue.getDetails().getCodings().get(index);
assertEquals(expectedCoding.getSystem(), actualCoding.getSystem());
assertEquals(expectedCoding.getCode(), actualCoding.getCode());
}
assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText());
assertEquals(expectedIssue.getDiagnostics(), actualIssue.getDiagnostics());
}
}

View File

@ -1,39 +0,0 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.server.IResourceProvider;
import org.hl7.fhir.instance.model.api.IBaseParameters;
public interface IValidationProviders {
String CODE_SYSTEM = "http://code.system/url";
String CODE_SYSTEM_VERSION = "1.0.0";
String CODE_SYSTEM_NAME = "Test Code System";
String CODE = "CODE";
String VALUE_SET_URL = "http://value.set/url";
String DISPLAY = "Explanation for code TestCode.";
String LANGUAGE = "en";
String ERROR_MESSAGE = "This is an error message";
interface IMyCodeSystemProvider extends IResourceProvider {
String getCode();
String getSystem();
String getDisplay();
void setException(Exception theException);
void setReturnParams(IBaseParameters theParameters);
}
interface IMyLookupCodeProvider extends IResourceProvider {
String getCode();
String getSystem();
void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult);
}
interface IMyValueSetProvider extends IResourceProvider {
String getCode();
String getSystem();
String getDisplay();
String getValueSet();
void setException(Exception theException);
void setReturnParams(IBaseParameters theParameters);
}
}

View File

@ -7,7 +7,6 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
@ -16,17 +15,18 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.junit.jupiter.api.Test;
import org.mockito.quality.Strictness;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import java.util.List;
public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestWithInlineMocks {
final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes();
@ -80,7 +80,7 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
}
@Test
public void validateCode_normally_resolvesCodeSystemFromValueSet() {
public void validateCode_codeInValueSet_resolvesCodeSystemFromValueSet() {
// setup
IValidationSupport validationSupport = mockValidationSupport();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
@ -90,8 +90,7 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
ValueSet valueSet = new ValueSet();
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(validationSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(valueSet);
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(new IValidationSupport.CodeValidationResult());
when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(mock(IValidationSupport.CodeValidationResult.class));
// execute
wrapper.validateCode(new ValidationOptions(), "code0", valueSet);
@ -101,6 +100,26 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any());
}
@Test
public void validateCode_codeNotInValueSet_doesNotResolveSystem() {
// setup
IValidationSupport validationSupport = mockValidationSupport();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached());
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer);
ValueSet valueSet = new ValueSet();
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
// execute
wrapper.validateCode(new ValidationOptions(), "code1", valueSet);
// verify
verify(validationSupport, times(1)).validateCodeInValueSet(any(), any(), eq(null), eq("code1"), any(), any());
verify(validationSupport, never()).validateCode(any(), any(), any(), any(), any(), any());
}
@Test
public void isPrimitive_primitive() {
// setup

View File

@ -4,7 +4,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks;
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
@ -28,10 +27,7 @@ import org.hl7.fhir.dstu2.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu2.model.QuestionnaireResponse;
import org.hl7.fhir.dstu2.model.QuestionnaireResponse.QuestionnaireResponseStatus;
import org.hl7.fhir.dstu2.model.StringType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -41,9 +37,7 @@ import org.mockito.stubbing.Answer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
@ -100,7 +94,7 @@ public class FhirInstanceValidatorDstu2Test extends BaseValidationTestWithInline
if (myValidConcepts.contains(system + "___" + code)) {
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) {
return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code");
return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code");
} else {
retVal = null;
}

View File

@ -58,7 +58,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -229,10 +228,10 @@ public class FhirInstanceValidatorDstu3Test extends BaseValidationTestWithInline
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
} else if (myValidSystemsNotReturningIssues.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message);
} else if (myCodeSystems.containsKey(system)) {
CodeSystem cs = myCodeSystems.get(system);
Optional<ConceptDefinitionComponent> found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst();

View File

@ -1,159 +0,0 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
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.api.server.RequestDetails;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
public interface IValidateCodeProvidersDstu3 {
@SuppressWarnings("unused")
class MyCodeSystemProviderDstu3 implements IValidationProviders.IMyCodeSystemProvider {
private UriType mySystemUrl;
private CodeType myCode;
private StringType myDisplay;
private Exception myException;
private Parameters myReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1),
@OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class),
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class)
})
public org.hl7.fhir.dstu3.model.Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) org.hl7.fhir.dstu3.model.IdType theId,
@OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) org.hl7.fhir.dstu3.model.CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) org.hl7.fhir.dstu3.model.StringType theDisplay
) throws Exception {
mySystemUrl = theCodeSystemUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name = "name", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1),
@OperationParam(name = "version", type = org.hl7.fhir.dstu3.model.StringType.class),
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1),
@OperationParam(name = "abstract", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1),
@OperationParam(name = "property", type = org.hl7.fhir.dstu3.model.StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED)
})
public IBaseParameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name = "code", max = 1) org.hl7.fhir.dstu3.model.CodeType theCode,
@OperationParam(name = "system",max = 1) org.hl7.fhir.dstu3.model.UriType theSystem,
@OperationParam(name = "coding", max = 1) Coding theCoding,
@OperationParam(name = "version", max = 1) org.hl7.fhir.dstu3.model.StringType theVersion,
@OperationParam(name = "displayLanguage", max = 1) org.hl7.fhir.dstu3.model.CodeType theDisplayLanguage,
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<org.hl7.fhir.dstu3.model.CodeType> thePropertyNames,
RequestDetails theRequestDetails
) {
myCode = theCode;
return myReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
}
}
@SuppressWarnings("unused")
class MyValueSetProviderDstu3 implements IValidationProviders.IMyValueSetProvider {
private Exception myException;
private Parameters myReturnParams;
private UriType mySystemUrl;
private UriType myValueSetUrl;
private CodeType myCode;
private StringType myDisplay;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class),
@OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.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") org.hl7.fhir.dstu3.model.ValueSet theValueSet
) throws Exception {
mySystemUrl = theSystem;
myValueSetUrl = theValueSetUrl;
myCode = theCode;
myDisplay = theDisplay;
if (myException != null) {
throw myException;
}
return myReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
public void setException(Exception theException) {
myException = theException;
}
@Override
public void setReturnParams(IBaseParameters theParameters) {
myReturnParams = (Parameters) theParameters;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
@Override
public String getValueSet() {
return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null;
}
public String getDisplay() {
return myDisplay != null ? myDisplay.getValue() : null;
}
}
}

View File

@ -41,7 +41,6 @@ import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@ -56,6 +55,8 @@ import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR;
import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.WARNING;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.BOOLEAN;
import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.CHOICE;
@ -224,7 +225,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class)))
.thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0"));
when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class)))
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code"));
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code"));
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
@ -246,7 +247,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class)))
.thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT));
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class)))
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode("warning").setMessage("Unknown code: http://codesystems.com/system / code1"));
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(WARNING).setMessage("Unknown code: http://codesystems.com/system / code1"));
QuestionnaireResponse qa;
@ -1034,7 +1035,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class)))
.thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0"));
when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class)))
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code"));
.thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code"));
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);

View File

@ -12,9 +12,9 @@ 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 ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
@ -164,8 +164,6 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
@SuppressWarnings("unused")
static class MyLookupCodeProviderDstu3 implements IValidationProviders.IMyLookupCodeProvider {
private UriType mySystemUrl;
private CodeType myCode;
private LookupCodeResult myLookupCodeResult;
@Override
@ -190,8 +188,6 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
@OperationParam(name= " property", max = OperationParam.MAX_UNLIMITED) List<StringType> thePropertyNames,
RequestDetails theRequestDetails
) {
myCode = theCode;
mySystemUrl = theSystem;
if (theSystem == null) {
throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode));
}
@ -205,15 +201,5 @@ public class RemoteTerminologyLookupCodeDstu3Test implements IRemoteTerminologyL
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
}
}

View File

@ -5,13 +5,10 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersDstu3;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -19,13 +16,15 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_LOOKUP;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RemoteTerminologyLookupCodeWithResponseFileDstu3Test {
private static final FhirContext ourCtx = FhirContext.forDstu3Cached();
private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider;
private IValidationProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider;
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
@ -36,7 +35,7 @@ public class RemoteTerminologyLookupCodeWithResponseFileDstu3Test {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3();
myCodeSystemProvider = new IValidationProvidersDstu3.MyCodeSystemProviderDstu3();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider);
}
@ -47,13 +46,10 @@ public class RemoteTerminologyLookupCodeWithResponseFileDstu3Test {
}
@Test
void lookupCode_withParametersOutput_convertsCorrectly() {
String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json");
IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString);
assertTrue(baseResource instanceof Parameters);
Parameters resultParameters = (Parameters) baseResource;
myCodeSystemProvider.setReturnParams(resultParameters);
String outputFile ="/terminology/CodeSystem-lookup-output-with-subproperties.json";
IBaseParameters resultParameters = myCodeSystemProvider.addTerminologyResponse(OPERATION_LOOKUP, CODE_SYSTEM, CODE, ourCtx, outputFile);
LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces"));
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces"));
// test
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request);

View File

@ -4,16 +4,18 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersDstu3;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterEach;
@ -22,6 +24,11 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET;
@ -38,8 +45,8 @@ public class RemoteTerminologyValidateCodeDstu3Test implements IRemoteTerminolog
private static final FhirContext ourCtx = FhirContext.forDstu3Cached();
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider;
private IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 myValueSetProvider;
private IValidationProviders.MyValidationProvider<CodeSystem> myCodeSystemProvider;
private IValidationProviders.MyValidationProvider<ValueSet> myValueSetProvider;
private RemoteTerminologyServiceValidationSupport mySvc;
private String myCodeSystemError, myValueSetError;
@ -48,14 +55,14 @@ public class RemoteTerminologyValidateCodeDstu3Test implements IRemoteTerminolog
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
myCodeSystemError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE);
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, CODE_SYSTEM, CODE, baseUrl, ERROR_MESSAGE);
myValueSetError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE);
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, CODE_SYSTEM, CODE, VALUE_SET_URL, baseUrl, ERROR_MESSAGE);
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3();
myValueSetProvider = new IValidateCodeProvidersDstu3.MyValueSetProviderDstu3();
myCodeSystemProvider = new IValidationProvidersDstu3.MyCodeSystemProviderDstu3();
myValueSetProvider = new IValidationProvidersDstu3.MyValueSetProviderDstu3();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider);
}
@ -82,45 +89,40 @@ public class RemoteTerminologyValidateCodeDstu3Test implements IRemoteTerminolog
}
@Override
public IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 getCodeSystemProvider() {
public IValidationProviders.IMyValidationProvider getCodeSystemProvider() {
return myCodeSystemProvider;
}
@Override
public IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 getValueSetProvider() {
public IValidationProviders.IMyValidationProvider getValueSetProvider() {
return myValueSetProvider;
}
@Override
public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json");
return getCodeSystemInvalidCodeOutcome(OperationOutcome.class);
}
@Override
public IBaseOperationOutcome getValueSetInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json");
return getValueSetInvalidCodeOutcome(OperationOutcome.class);
}
@Override
public IBaseOperationOutcome getValueSetCustomDetailCodeOutcome() {
return getValueSetCustomDetailCodeOutcome(OperationOutcome.class);
}
@Override
public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
Parameters parameters = new Parameters();
parameters.addParameter().setName("result").setValue(new BooleanType(theResult));
parameters.addParameter().setName("code").setValue(new StringType(IValidationProviders.CODE));
parameters.addParameter().setName("system").setValue(new UriType(IValidationProviders.CODE_SYSTEM));
parameters.addParameter().setName("version").setValue(new StringType(IValidationProviders.CODE_SYSTEM_VERSION));
parameters.addParameter().setName("code").setValue(new StringType(CODE));
parameters.addParameter().setName("system").setValue(new UriType(CODE_SYSTEM));
parameters.addParameter().setName("version").setValue(new StringType(CODE_SYSTEM_VERSION));
parameters.addParameter().setName("display").setValue(new StringType(theDisplay));
parameters.addParameter().setName("message").setValue(new StringType(theMessage));
parameters.addParameter().setName("issues").setResource((Resource) theIssuesResource);
return parameters;
}
@Override
public void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
myCodeSystemProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
@Override
public void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
myValueSetProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource));
}
}

View File

@ -307,10 +307,10 @@ public class FhirInstanceValidatorR4Test extends BaseValidationTestWithInlineMoc
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
} else if (myValidSystemsNotReturningIssues.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message);
} else {
retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl);
}

View File

@ -11,9 +11,10 @@ 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 ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
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;
@ -52,7 +53,7 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
private IValidationProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
private MyLookupCodeProviderR4 myLookupCodeProviderR4;
@BeforeEach
@ -60,7 +61,7 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc.setBaseUrl(baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(true));
myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4();
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
myLookupCodeProviderR4 = new MyLookupCodeProviderR4();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myLookupCodeProviderR4);
}
@ -166,8 +167,6 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
@SuppressWarnings("unused")
static class MyLookupCodeProviderR4 implements IValidationProviders.IMyLookupCodeProvider {
private UriType mySystemUrl;
private CodeType myCode;
private LookupCodeResult myLookupCodeResult;
@Override
@ -192,8 +191,6 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
@OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List<CodeType> thePropertyNames,
RequestDetails theRequestDetails
) {
myCode = theCode;
mySystemUrl = theSystem;
if (theSystem == null) {
throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode));
}
@ -206,15 +203,5 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
@Override
public String getCode() {
return myCode != null ? myCode.getValueAsString() : null;
}
@Override
public String getSystem() {
return mySystemUrl != null ? mySystemUrl.getValueAsString() : null;
}
}
}

View File

@ -5,12 +5,9 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.LookupCodeRequest;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -19,13 +16,15 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.List;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_LOOKUP;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RemoteTerminologyLookupCodeWithResponseFileR4Test {
private static final FhirContext ourCtx = FhirContext.forR4Cached();
private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
private IValidationProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
@ -36,7 +35,7 @@ public class RemoteTerminologyLookupCodeWithResponseFileR4Test {
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4();
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider);
}
@ -48,13 +47,10 @@ public class RemoteTerminologyLookupCodeWithResponseFileR4Test {
@Test
void lookupCode_withParametersOutput_convertsCorrectly() {
String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json");
IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString);
assertTrue(baseResource instanceof Parameters);
Parameters resultParameters = (Parameters) baseResource;
myCodeSystemProvider.setReturnParams(resultParameters);
String outputFile ="/terminology/CodeSystem-lookup-output-with-subproperties.json";
IBaseParameters resultParameters = myCodeSystemProvider.addTerminologyResponse(OPERATION_LOOKUP, CODE_SYSTEM, CODE, ourCtx, outputFile);
LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces"));
LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces"));
// test
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request);

View File

@ -2,8 +2,8 @@ package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.parser.IJsonLikeParser;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
@ -13,11 +13,11 @@ 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 ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.test.utilities.validation.IValidationProviders;
import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.collect.Lists;
import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest;
import org.hl7.fhir.common.hapi.validation.IValidationProviders;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -39,6 +39,12 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE;
import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM;
import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET;
@ -61,8 +67,8 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
private static final FhirContext ourCtx = FhirContext.forR4Cached();
@RegisterExtension
public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx);
private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider;
private IValidateCodeProvidersR4.MyValueSetProviderR4 myValueSetProvider;
private IValidationProviders.IMyValidationProvider myCodeSystemProvider;
private IValidationProviders.IMyValidationProvider myValueSetProvider;
private RemoteTerminologyServiceValidationSupport mySvc;
private String myCodeSystemError, myValueSetError;
@ -71,14 +77,14 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort();
myCodeSystemError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE);
ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, CODE_SYSTEM, CODE, baseUrl, ERROR_MESSAGE);
myValueSetError = ourCtx.getLocalizer().getMessage(
RemoteTerminologyServiceValidationSupport.class,
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE);
ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, CODE_SYSTEM, CODE, VALUE_SET_URL, baseUrl, ERROR_MESSAGE);
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true));
myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4();
myValueSetProvider = new IValidateCodeProvidersR4.MyValueSetProviderR4();
myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4();
myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4();
ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider);
}
@ -95,12 +101,12 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
}
@Override
public IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider() {
public IValidationProviders.IMyValidationProvider getCodeSystemProvider() {
return myCodeSystemProvider;
}
@Override
public IValidationProviders.IMyValueSetProvider getValueSetProvider() {
public IValidationProviders.IMyValidationProvider getValueSetProvider() {
return myValueSetProvider;
}
@ -116,51 +122,40 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
@Override
public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json");
return getCodeSystemInvalidCodeOutcome(OperationOutcome.class);
}
@Override
public IBaseOperationOutcome getValueSetInvalidCodeOutcome() {
return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json");
return getValueSetInvalidCodeOutcome(OperationOutcome.class);
}
@Override
public List<IValidationSupport.CodeValidationIssue> getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) {
return ((OperationOutcome)theOperationOutcome).getIssue().stream()
.map(issueComponent -> new IValidationSupport.CodeValidationIssue(
issueComponent.getDetails().getText(),
IValidationSupport.IssueSeverity.ERROR,
/* assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match */
IValidationSupport.CodeValidationIssueCode.INVALID,
IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))
.toList();
public IBaseOperationOutcome getValueSetCustomDetailCodeOutcome() {
return getValueSetCustomDetailCodeOutcome(OperationOutcome.class);
}
@Test
void validateCodeInValueSet_success() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
createValueSetReturnParameters(true, DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setUrl(VALUE_SET_URL);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), CODE_SYSTEM, CODE, DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(IValidationProviders.CODE, outcome.getCode());
assertEquals(IValidationProviders.DISPLAY, outcome.getDisplay());
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertNull(outcome.getSeverity());
assertNull(outcome.getMessage());
assertEquals(IValidationProviders.CODE, myValueSetProvider.getCode());
assertEquals(IValidationProviders.DISPLAY, myValueSetProvider.getDisplay());
assertEquals(IValidationProviders.VALUE_SET_URL, myValueSetProvider.getValueSet());
}
@Override
public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) {
Parameters parameters = new Parameters()
.addParameter("code", IValidationProviders.CODE)
.addParameter("system", IValidationProviders.CODE_SYSTEM)
.addParameter("version", IValidationProviders.CODE_SYSTEM_VERSION)
.addParameter("code", CODE)
.addParameter("system", CODE_SYSTEM)
.addParameter("version", CODE_SYSTEM_VERSION)
.addParameter("display", theDisplay)
.addParameter("message", theMessage);
if (theResult != null) {
@ -181,16 +176,16 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
@Test
void validateCodeInValueSet_uniqueComposeInclude() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
createValueSetReturnParameters(true, DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Collections.singletonList(new ValueSet.ConceptSetComponent().setSystem(systemUrl)) ));
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
// validate service doesn't return error message (as when no code system is present)
assertNotNull(outcome);
@ -211,16 +206,16 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerExceptions")
void validateCodeInValueSet_systemNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.setException(theException);
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
getValueSetProvider().addException("$validate-code", VALUE_SET_URL, CODE, theException);
createValueSetReturnParameters(true, DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setUrl(VALUE_SET_URL);
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent())));
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage);
@ -230,11 +225,11 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
@ParameterizedTest
@MethodSource(value = "getRemoteTerminologyServerExceptions")
void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) {
myValueSetProvider.setException(theException);
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
getValueSetProvider().addException(JpaConstants.OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, theException);
createValueSetReturnParameters(true, DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
@ -243,7 +238,7 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
new ValueSet.ConceptSetComponent().setSystem(systemUrl2))));
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://";
verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage);
@ -252,10 +247,10 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
@Test
void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
createValueSetReturnParameters(true, DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(
@ -264,14 +259,14 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setConcept(
Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode("not-the-code"),
new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) )
new ValueSet.ConceptReferenceComponent().setCode(CODE) )
)) ));
TestClientInterceptor requestInterceptor = new TestClientInterceptor();
mySvc.addClientInterceptor(requestInterceptor);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(systemUrl2, requestInterceptor.getCapturedSystemParameter());
@ -280,10 +275,10 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
@Test
void validateCodeInValueSet_systemPresentCodePresentValidatesOKVersioned() {
createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null);
createValueSetReturnParameters(true, DISPLAY, null, null);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(IValidationProviders.VALUE_SET_URL);
valueSet.setUrl(VALUE_SET_URL);
String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String systemVersion = "3.0.2";
String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset";
@ -294,14 +289,14 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa
new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setVersion(system2Version).setConcept(
Lists.newArrayList(
new ValueSet.ConceptReferenceComponent().setCode("not-the-code"),
new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) )
new ValueSet.ConceptReferenceComponent().setCode(CODE) )
)) ));
TestClientInterceptor requestInterceptor = new TestClientInterceptor();
mySvc.addClientInterceptor(requestInterceptor);
CodeValidationResult outcome = mySvc.validateCodeInValueSet(null,
new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet);
new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet);
assertNotNull(outcome);
assertEquals(systemUrl2 + "|" + system2Version, requestInterceptor.getCapturedSystemParameter());

View File

@ -31,6 +31,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r4b.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4b.model.AllergyIntolerance;
import org.hl7.fhir.r4b.model.Base;
@ -61,7 +62,6 @@ import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander;
import org.hl7.fhir.r4b.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
@ -203,7 +203,7 @@ public class FhirInstanceValidatorR4BTest extends BaseValidationTestWithInlineMo
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
} else {
retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl);
}

View File

@ -48,7 +48,6 @@ import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
@ -200,10 +199,10 @@ public class FhirInstanceValidatorR5Test extends BaseValidationTestWithInlineMoc
retVal = new IValidationSupport.CodeValidationResult().setCode(code);
} else if (myValidSystems.contains(system)) {
String theMessage = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)));
} else if (myValidSystemsNotReturningIssues.contains(system)) {
final String message = "Unknown code (for '" + system + "#" + code + "')";
retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message);
retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message);
} else {
retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl);
}

View File

@ -0,0 +1,22 @@
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "processing",
"details": {
"coding": [
{
"system": "http://example.com/custom-issue-type",
"code": "valueset-is-draft"
}
]
},
"diagnostics": "The ValueSet status is marked as draft.",
"location": [
"Bundle",
"Line[1] Col[2]"
]
}
]
}