Improve terminology validation error messages (#5271)
* Improve terminology validation error messages * Changelog * Validation issues * Test fixes * Cleanup * Review comments * Fix
This commit is contained in:
parent
3e46f71542
commit
a676506fc5
|
@ -691,8 +691,9 @@ public interface IValidationSupport {
|
|||
private boolean myFound;
|
||||
private String mySearchedForCode;
|
||||
private String mySearchedForSystem;
|
||||
private List<IValidationSupport.BaseConceptProperty> myProperties;
|
||||
private List<BaseConceptProperty> myProperties;
|
||||
private List<ConceptDesignation> myDesignations;
|
||||
private String myErrorMessage;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -708,7 +709,7 @@ public interface IValidationSupport {
|
|||
return myProperties;
|
||||
}
|
||||
|
||||
public void setProperties(List<IValidationSupport.BaseConceptProperty> theProperties) {
|
||||
public void setProperties(List<BaseConceptProperty> theProperties) {
|
||||
myProperties = theProperties;
|
||||
}
|
||||
|
||||
|
@ -808,7 +809,7 @@ public interface IValidationSupport {
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
for (IValidationSupport.BaseConceptProperty next : myProperties) {
|
||||
for (BaseConceptProperty next : myProperties) {
|
||||
|
||||
if (!properties.isEmpty()) {
|
||||
if (!properties.contains(next.getPropertyName())) {
|
||||
|
@ -819,11 +820,11 @@ public interface IValidationSupport {
|
|||
IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
|
||||
ParametersUtil.addPartCode(theContext, property, "code", next.getPropertyName());
|
||||
|
||||
if (next instanceof IValidationSupport.StringConceptProperty) {
|
||||
IValidationSupport.StringConceptProperty prop = (IValidationSupport.StringConceptProperty) next;
|
||||
if (next instanceof StringConceptProperty) {
|
||||
StringConceptProperty prop = (StringConceptProperty) next;
|
||||
ParametersUtil.addPartString(theContext, property, "value", prop.getValue());
|
||||
} else if (next instanceof IValidationSupport.CodingConceptProperty) {
|
||||
IValidationSupport.CodingConceptProperty prop = (IValidationSupport.CodingConceptProperty) next;
|
||||
} else if (next instanceof CodingConceptProperty) {
|
||||
CodingConceptProperty prop = (CodingConceptProperty) next;
|
||||
ParametersUtil.addPartCoding(
|
||||
theContext, property, "value", prop.getCodeSystem(), prop.getCode(), prop.getDisplay());
|
||||
} else {
|
||||
|
@ -846,6 +847,14 @@ public interface IValidationSupport {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String theErrorMessage) {
|
||||
myErrorMessage = theErrorMessage;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return myErrorMessage;
|
||||
}
|
||||
|
||||
public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
|
||||
return new LookupCodeResult()
|
||||
.setFound(false)
|
||||
|
|
|
@ -23,6 +23,7 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
|||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
@ -250,34 +251,77 @@ public class OperationOutcomeUtil {
|
|||
public static IBase addIssueWithMessageId(
|
||||
FhirContext myCtx,
|
||||
IBaseOperationOutcome theOperationOutcome,
|
||||
String severity,
|
||||
String message,
|
||||
String messageId,
|
||||
String location,
|
||||
String theSeverity,
|
||||
String theMessage,
|
||||
String theMessageId,
|
||||
String theLocation,
|
||||
String theCode) {
|
||||
IBase issue = addIssue(myCtx, theOperationOutcome, severity, message, location, theCode);
|
||||
BaseRuntimeElementCompositeDefinition<?> issueElement =
|
||||
(BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition(issue.getClass());
|
||||
BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details");
|
||||
IBase issue = addIssue(myCtx, theOperationOutcome, theSeverity, theMessage, theLocation, theCode);
|
||||
if (isNotBlank(theMessageId)) {
|
||||
addDetailsToIssue(myCtx, issue, Constants.JAVA_VALIDATOR_DETAILS_SYSTEM, theMessageId);
|
||||
}
|
||||
|
||||
IPrimitiveType<?> system =
|
||||
(IPrimitiveType<?>) myCtx.getElementDefinition("uri").newInstance();
|
||||
system.setValueAsString(Constants.JAVA_VALIDATOR_DETAILS_SYSTEM);
|
||||
IPrimitiveType<?> code =
|
||||
(IPrimitiveType<?>) myCtx.getElementDefinition("code").newInstance();
|
||||
code.setValueAsString(messageId);
|
||||
|
||||
BaseRuntimeElementCompositeDefinition<?> codingDef =
|
||||
(BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition("Coding");
|
||||
ICompositeType coding = (ICompositeType) codingDef.newInstance();
|
||||
codingDef.getChildByName("system").getMutator().addValue(coding, system);
|
||||
codingDef.getChildByName("code").getMutator().addValue(coding, code);
|
||||
BaseRuntimeElementCompositeDefinition<?> ccDef =
|
||||
(BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition("CodeableConcept");
|
||||
ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance();
|
||||
ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding);
|
||||
|
||||
detailsChildDef.getMutator().addValue(issue, codeableConcept);
|
||||
return issue;
|
||||
}
|
||||
|
||||
public static void addDetailsToIssue(FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode) {
|
||||
BaseRuntimeElementCompositeDefinition<?> issueElement =
|
||||
(BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theIssue.getClass());
|
||||
BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details");
|
||||
|
||||
BaseRuntimeElementCompositeDefinition<?> codingDef =
|
||||
(BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("Coding");
|
||||
ICompositeType coding = (ICompositeType) codingDef.newInstance();
|
||||
|
||||
// System
|
||||
IPrimitiveType<?> system =
|
||||
(IPrimitiveType<?>) theFhirContext.getElementDefinition("uri").newInstance();
|
||||
system.setValueAsString(theSystem);
|
||||
codingDef.getChildByName("system").getMutator().addValue(coding, system);
|
||||
|
||||
// Code
|
||||
IPrimitiveType<?> code =
|
||||
(IPrimitiveType<?>) theFhirContext.getElementDefinition("code").newInstance();
|
||||
code.setValueAsString(theCode);
|
||||
codingDef.getChildByName("code").getMutator().addValue(coding, code);
|
||||
BaseRuntimeElementCompositeDefinition<?> ccDef =
|
||||
(BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("CodeableConcept");
|
||||
|
||||
ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance();
|
||||
ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding);
|
||||
detailsChildDef.getMutator().addValue(theIssue, codeableConcept);
|
||||
}
|
||||
|
||||
public static void addIssueLineExtensionToIssue(FhirContext theCtx, IBase theIssue, String theLine) {
|
||||
if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) {
|
||||
ExtensionUtil.setExtension(
|
||||
theCtx,
|
||||
theIssue,
|
||||
"http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line",
|
||||
"integer",
|
||||
theLine);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addIssueColExtensionToIssue(FhirContext theCtx, IBase theIssue, String theColumn) {
|
||||
if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) {
|
||||
ExtensionUtil.setExtension(
|
||||
theCtx,
|
||||
theIssue,
|
||||
"http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col",
|
||||
"integer",
|
||||
theColumn);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addMessageIdExtensionToIssue(FhirContext theCtx, IBase theIssue, String theMessageId) {
|
||||
if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) {
|
||||
ExtensionUtil.setExtension(
|
||||
theCtx,
|
||||
theIssue,
|
||||
"http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id",
|
||||
"string",
|
||||
theMessageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
|
|||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SingleValidationMessage {
|
||||
|
||||
private Integer myLocationCol;
|
||||
|
@ -32,6 +34,7 @@ public class SingleValidationMessage {
|
|||
private String myMessage;
|
||||
private String myMessageId;
|
||||
private ResultSeverityEnum mySeverity;
|
||||
private List<String> mySliceMessages;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -58,6 +61,7 @@ public class SingleValidationMessage {
|
|||
b.append(myLocationString, other.myLocationString);
|
||||
b.append(myMessage, other.myMessage);
|
||||
b.append(mySeverity, other.mySeverity);
|
||||
b.append(mySliceMessages, other.mySliceMessages);
|
||||
return b.isEquals();
|
||||
}
|
||||
|
||||
|
@ -93,6 +97,7 @@ public class SingleValidationMessage {
|
|||
b.append(myLocationString);
|
||||
b.append(myMessage);
|
||||
b.append(mySeverity);
|
||||
b.append(mySliceMessages);
|
||||
return b.toHashCode();
|
||||
}
|
||||
|
||||
|
@ -137,6 +142,17 @@ public class SingleValidationMessage {
|
|||
if (mySeverity != null) {
|
||||
b.append("severity", mySeverity.getCode());
|
||||
}
|
||||
if (mySliceMessages != null) {
|
||||
b.append("sliceMessages", mySliceMessages);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public void setSliceMessages(List<String> theSliceMessages) {
|
||||
mySliceMessages = theSliceMessages;
|
||||
}
|
||||
|
||||
public List<String> getSliceMessages() {
|
||||
return mySliceMessages;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
|
@ -38,11 +39,11 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
*/
|
||||
public class ValidationResult {
|
||||
public static final int ERROR_DISPLAY_LIMIT_DEFAULT = 1;
|
||||
|
||||
public static final String UNKNOWN = "(unknown)";
|
||||
private static final String ourNewLine = System.getProperty("line.separator");
|
||||
private final FhirContext myCtx;
|
||||
private final boolean myIsSuccessful;
|
||||
private final List<SingleValidationMessage> myMessages;
|
||||
|
||||
private int myErrorDisplayLimit = ERROR_DISPLAY_LIMIT_DEFAULT;
|
||||
|
||||
public ValidationResult(FhirContext theCtx, List<SingleValidationMessage> theMessages) {
|
||||
|
@ -108,8 +109,8 @@ public class ValidationResult {
|
|||
|
||||
/**
|
||||
* @deprecated Use {@link #toOperationOutcome()} instead since this method returns a view.
|
||||
* {@link #toOperationOutcome()} is identical to this method, but has a more suitable name so this method
|
||||
* will be removed at some point.
|
||||
* {@link #toOperationOutcome()} is identical to this method, but has a more suitable name so this method
|
||||
* will be removed at some point.
|
||||
*/
|
||||
@Deprecated
|
||||
public IBaseOperationOutcome getOperationOutcome() {
|
||||
|
@ -131,39 +132,36 @@ public class ValidationResult {
|
|||
*/
|
||||
public void populateOperationOutcome(IBaseOperationOutcome theOperationOutcome) {
|
||||
for (SingleValidationMessage next : myMessages) {
|
||||
String location;
|
||||
if (isNotBlank(next.getLocationString())) {
|
||||
location = next.getLocationString();
|
||||
} else if (next.getLocationLine() != null || next.getLocationCol() != null) {
|
||||
location = "Line[" + next.getLocationLine() + "] Col[" + next.getLocationCol() + "]";
|
||||
} else {
|
||||
location = null;
|
||||
}
|
||||
String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null;
|
||||
IBase issue = OperationOutcomeUtil.addIssueWithMessageId(
|
||||
myCtx,
|
||||
theOperationOutcome,
|
||||
severity,
|
||||
next.getMessage(),
|
||||
next.getMessageId(),
|
||||
location,
|
||||
Constants.OO_INFOSTATUS_PROCESSING);
|
||||
Integer locationLine = next.getLocationLine();
|
||||
Integer locationCol = next.getLocationCol();
|
||||
String location = next.getLocationString();
|
||||
ResultSeverityEnum issueSeverity = next.getSeverity();
|
||||
String message = next.getMessage();
|
||||
String messageId = next.getMessageId();
|
||||
|
||||
if (next.getLocationLine() != null || next.getLocationCol() != null) {
|
||||
String unknown = "(unknown)";
|
||||
String line = unknown;
|
||||
if (next.getLocationLine() != null && next.getLocationLine() != -1) {
|
||||
line = next.getLocationLine().toString();
|
||||
}
|
||||
String col = unknown;
|
||||
if (next.getLocationCol() != null && next.getLocationCol() != -1) {
|
||||
col = next.getLocationCol().toString();
|
||||
}
|
||||
if (!unknown.equals(line) || !unknown.equals(col)) {
|
||||
OperationOutcomeUtil.addLocationToIssue(myCtx, issue, "Line " + line + ", Col " + col);
|
||||
}
|
||||
if (next.getSliceMessages() == null) {
|
||||
addIssueToOperationOutcome(
|
||||
theOperationOutcome, location, locationLine, locationCol, issueSeverity, message, messageId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Occasionally the validator will return these lists of "slice messages"
|
||||
* which happen when validating rules associated with a specific slice in
|
||||
* a profile.
|
||||
*/
|
||||
for (String nextSliceMessage : next.getSliceMessages()) {
|
||||
String combinedMessage = message + " - " + nextSliceMessage;
|
||||
addIssueToOperationOutcome(
|
||||
theOperationOutcome,
|
||||
location,
|
||||
locationLine,
|
||||
locationCol,
|
||||
issueSeverity,
|
||||
combinedMessage,
|
||||
messageId);
|
||||
}
|
||||
} // for
|
||||
|
||||
if (myMessages.isEmpty()) {
|
||||
String message = myCtx.getLocalizer().getMessage(ValidationResult.class, "noIssuesDetected");
|
||||
|
@ -171,6 +169,44 @@ public class ValidationResult {
|
|||
}
|
||||
}
|
||||
|
||||
private void addIssueToOperationOutcome(
|
||||
IBaseOperationOutcome theOperationOutcome,
|
||||
String location,
|
||||
Integer locationLine,
|
||||
Integer locationCol,
|
||||
ResultSeverityEnum issueSeverity,
|
||||
String message,
|
||||
String messageId) {
|
||||
if (isBlank(location) && locationLine != null && locationCol != null) {
|
||||
location = "Line[" + locationLine + "] Col[" + locationCol + "]";
|
||||
}
|
||||
String severity = issueSeverity != null ? issueSeverity.getCode() : null;
|
||||
IBase issue = OperationOutcomeUtil.addIssueWithMessageId(
|
||||
myCtx, theOperationOutcome, severity, message, messageId, location, Constants.OO_INFOSTATUS_PROCESSING);
|
||||
|
||||
if (locationLine != null || locationCol != null) {
|
||||
String unknown = UNKNOWN;
|
||||
String line = unknown;
|
||||
if (locationLine != null && locationLine != -1) {
|
||||
line = locationLine.toString();
|
||||
}
|
||||
String col = unknown;
|
||||
if (locationCol != null && locationCol != -1) {
|
||||
col = locationCol.toString();
|
||||
}
|
||||
if (!unknown.equals(line) || !unknown.equals(col)) {
|
||||
OperationOutcomeUtil.addIssueLineExtensionToIssue(myCtx, issue, line);
|
||||
OperationOutcomeUtil.addIssueColExtensionToIssue(myCtx, issue, col);
|
||||
String locationString = "Line[" + line + "] Col[" + col + "]";
|
||||
OperationOutcomeUtil.addLocationToIssue(myCtx, issue, locationString);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotBlank(messageId)) {
|
||||
OperationOutcomeUtil.addMessageIdExtensionToIssue(myCtx, issue, messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValidationResult{" + "messageCount=" + myMessages.size() + ", isSuccessful=" + myIsSuccessful
|
||||
|
@ -191,6 +227,4 @@ public class ValidationResult {
|
|||
public void setErrorDisplayLimit(int theErrorDisplayLimit) {
|
||||
myErrorDisplayLimit = theErrorDisplayLimit;
|
||||
}
|
||||
|
||||
private static final String ourNewLine = System.getProperty("line.separator");
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ ca.uhn.fhir.jpa.term.TermReadSvcImpl.validationPerformedAgainstPreExpansion=Code
|
|||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotFoundInTerminologyDatabase=ValueSet can not be found in terminology database: {0}
|
||||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetPreExpansionInvalidated=ValueSet with URL "{0}" precaluclated expansion with {1} concept(s) has been invalidated
|
||||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetCantInvalidateNotYetPrecalculated=ValueSet with URL "{0}" already has status: {1}
|
||||
ca.uhn.fhir.jpa.term.TermReadSvcImpl.unknownCodeInSystem=Unknown code "{0}#{1}"
|
||||
|
||||
|
||||
# Core Library Messages
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: add
|
||||
issue: 5271
|
||||
title: "The error messages returned in an OperationOutcome when validating terminology codes
|
||||
as a part of resource profile validation have been improved. Machine processable location
|
||||
(line/col) information is now available through a pair of dedicated extensions, and
|
||||
error messages such as UCUM parsing issues are now returned to the client (previously
|
||||
they were swallowed and a generic error message was returned)."
|
|
@ -2093,7 +2093,10 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
|
|||
if (outcome.size() == 0) {
|
||||
append = " - No codes in ValueSet belong to CodeSystem with URL " + theSystem;
|
||||
} else {
|
||||
append = " - Unknown code " + theSystem + "#" + theCode + ". " + msg;
|
||||
String unknownCodeMessage = myContext
|
||||
.getLocalizer()
|
||||
.getMessage(TermReadSvcImpl.class, "unknownCodeInSystem", theSystem, theCode);
|
||||
append = " - " + unknownCodeMessage + ". " + msg;
|
||||
}
|
||||
|
||||
return createFailureCodeValidationResult(theSystem, theCode, null, append);
|
||||
|
|
|
@ -23,6 +23,7 @@ import ca.uhn.fhir.parser.StrictErrorHandler;
|
|||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.ValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
|
@ -30,48 +31,21 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import ca.uhn.fhir.validation.ValidationResult;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport;
|
||||
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.AllergyIntolerance;
|
||||
import org.hl7.fhir.r4.model.Binary;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.r4.model.CanonicalType;
|
||||
import org.hl7.fhir.r4.model.CapabilityStatement;
|
||||
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.Condition;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.ElementDefinition;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.Group;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Location;
|
||||
import org.hl7.fhir.r4.model.Narrative;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.Questionnaire;
|
||||
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.SearchParameter;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.hl7.fhir.r4.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4.model.UriType;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
|
||||
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -86,9 +60,8 @@ import java.util.stream.Collectors;
|
|||
import static ca.uhn.fhir.rest.api.Constants.JAVA_VALIDATOR_DETAILS_SYSTEM;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.CURRENCIES_CODESYSTEM_URL;
|
||||
import static org.hl7.fhir.common.hapi.validation.support.ValidationConstants.LOINC_LOW;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
@ -368,6 +341,14 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
assertThat(oo.getIssue().get(0).getDiagnostics(),
|
||||
containsString("HAPI-0702: Unable to expand ValueSet because CodeSystem could not be found: http://cs"));
|
||||
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity());
|
||||
assertEquals(27, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue());
|
||||
assertEquals(4, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue());
|
||||
assertEquals("Terminology_TX_Confirm_4a", ((StringType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue());
|
||||
assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode());
|
||||
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||
assertEquals(2, oo.getIssue().get(0).getLocation().size());
|
||||
assertEquals("Observation.value.ofType(Quantity)", oo.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line[27] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue());
|
||||
|
||||
}
|
||||
|
||||
|
@ -1272,7 +1253,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
|
||||
private <T extends IBaseResource> OperationOutcome validateAndReturnOutcome(T theObs, Boolean theWantError) {
|
||||
IFhirResourceDao<T> dao = (IFhirResourceDao<T>) myDaoRegistry.getResourceDao(theObs.getClass());
|
||||
MethodOutcome outcome = dao.validate(theObs, null, null, null, ValidationModeEnum.CREATE, null, mySrd);
|
||||
String encoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(theObs);
|
||||
MethodOutcome outcome = dao.validate(theObs, null, encoded, EncodingEnum.JSON, ValidationModeEnum.CREATE, null, mySrd);
|
||||
OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome();
|
||||
if (theWantError) {
|
||||
assertHasErrors(oo);
|
||||
|
@ -1596,6 +1578,66 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testValidateCommonCodes_Ucum_ErrorMessageIsPreserved() {
|
||||
Observation input = new Observation();
|
||||
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||
input.setStatus(ObservationStatus.AMENDED);
|
||||
input.getCode().addCoding().setSystem("http://loinc.org").setCode("1234").setDisplay("FOO");
|
||||
input.setValue(new Quantity(
|
||||
null,
|
||||
123,
|
||||
"http://unitsofmeasure.org",
|
||||
"MG/DL",
|
||||
"MG/DL"
|
||||
));
|
||||
|
||||
String inputString = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
|
||||
MethodOutcome result = myObservationDao.validate(input, null, inputString, EncodingEnum.JSON, ValidationModeEnum.CREATE, null, mySrd);
|
||||
OperationOutcome oo = (OperationOutcome) result.getOperationOutcome();
|
||||
assertHasErrors(oo);
|
||||
|
||||
assertEquals(15, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue());
|
||||
assertEquals(4, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue());
|
||||
assertEquals("Terminology_PassThrough_TX_Message", ((StringType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue());
|
||||
assertEquals("Error processing unit 'MG/DL': The unit 'DL' is unknown' at position 3 for 'http://unitsofmeasure.org#MG/DL'", oo.getIssue().get(0).getDiagnostics());
|
||||
assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode());
|
||||
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||
assertEquals(2, oo.getIssue().get(0).getLocation().size());
|
||||
assertEquals("Observation.value.ofType(Quantity)", oo.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line[15] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateCommonCodes_Currency_ErrorMessageIsPreserved() {
|
||||
Observation input = new Observation();
|
||||
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||
input.setStatus(ObservationStatus.AMENDED);
|
||||
input.getCode().addCoding().setSystem("http://loinc.org").setCode("1234").setDisplay("FOO");
|
||||
input.setValue(new Quantity(
|
||||
null,
|
||||
123,
|
||||
CURRENCIES_CODESYSTEM_URL,
|
||||
"blah",
|
||||
"blah"
|
||||
));
|
||||
|
||||
String inputString = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
|
||||
MethodOutcome result = myObservationDao.validate(input, null, inputString, EncodingEnum.JSON, ValidationModeEnum.CREATE, null, mySrd);
|
||||
OperationOutcome oo = (OperationOutcome) result.getOperationOutcome();
|
||||
assertHasErrors(oo);
|
||||
|
||||
assertEquals(15, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue());
|
||||
assertEquals(4, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue());
|
||||
assertEquals("Terminology_PassThrough_TX_Message", ((StringType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue());
|
||||
assertEquals("Unknown code 'urn:iso:std:iso:4217#blah' for 'urn:iso:std:iso:4217#blah'", oo.getIssue().get(0).getDiagnostics());
|
||||
assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode());
|
||||
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||
assertEquals(2, oo.getIssue().get(0).getLocation().size());
|
||||
assertEquals("Observation.value.ofType(Quantity)", oo.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line[15] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateForCreate() {
|
||||
String methodName = "testValidateForCreate";
|
||||
|
|
|
@ -182,7 +182,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "childX", null, "http://vs");
|
||||
assertNotNull(outcome);
|
||||
assertFalse(outcome.isOk());
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code http://cs#childX"));
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code \"http://cs#childX\""));
|
||||
assertThat(outcome.getMessage(), containsString("Code validation occurred using a ValueSet expansion that was pre-calculated at "));
|
||||
|
||||
// Precalculated - Enumerated in non-present CS
|
||||
|
@ -195,7 +195,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
outcome = myValidationSupport.validateCode(ctx, options, "http://cs-np", "codeX", null, "http://vs");
|
||||
assertNotNull(outcome);
|
||||
assertFalse(outcome.isOk());
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code http://cs-np#codeX"));
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code \"http://cs-np#codeX\""));
|
||||
assertThat(outcome.getMessage(), containsString("Code validation occurred using a ValueSet expansion that was pre-calculated at "));
|
||||
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "childX", null, "http://vs");
|
||||
assertNotNull(outcome);
|
||||
assertFalse(outcome.isOk());
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code http://cs#childX"));
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code \"http://cs#childX\""));
|
||||
assertThat(outcome.getMessage(), containsString("Code validation occurred using a ValueSet expansion that was pre-calculated at "));
|
||||
|
||||
// Precalculated - Enumerated in non-present CS
|
||||
|
@ -298,7 +298,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
outcome = myValidationSupport.validateCode(ctx, options, "http://cs-np", "codeX", null, "http://vs");
|
||||
assertNotNull(outcome);
|
||||
assertFalse(outcome.isOk());
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code http://cs-np#codeX"));
|
||||
assertThat(outcome.getMessage(), containsString("Unknown code \"http://cs-np#codeX\""));
|
||||
assertThat(outcome.getMessage(), containsString("Code validation occurred using a ValueSet expansion that was pre-calculated at "));
|
||||
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import java.util.Optional;
|
|||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -124,8 +125,8 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
break;
|
||||
|
||||
case LANGUAGES_VALUESET_URL:
|
||||
if (!LANGUAGES_CODESYSTEM_URL.equals(theCodeSystem)
|
||||
&& !(theCodeSystem == null && theOptions.isInferSystem())) {
|
||||
expectSystem = LANGUAGES_CODESYSTEM_URL;
|
||||
if (!expectSystem.equals(theCodeSystem) && !(theCodeSystem == null && theOptions.isInferSystem())) {
|
||||
return new CodeValidationResult()
|
||||
.setSeverity(IssueSeverity.ERROR)
|
||||
.setMessage("Inappropriate CodeSystem URL \"" + theCodeSystem + "\" for ValueSet: "
|
||||
|
@ -155,8 +156,8 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
}
|
||||
|
||||
case ALL_LANGUAGES_VALUESET_URL:
|
||||
if (!LANGUAGES_CODESYSTEM_URL.equals(theCodeSystem)
|
||||
&& !(theCodeSystem == null && theOptions.isInferSystem())) {
|
||||
expectSystem = LANGUAGES_CODESYSTEM_URL;
|
||||
if (!expectSystem.equals(theCodeSystem) && !(theCodeSystem == null && theOptions.isInferSystem())) {
|
||||
return new CodeValidationResult()
|
||||
.setSeverity(IssueSeverity.ERROR)
|
||||
.setMessage("Inappropriate CodeSystem URL \"" + theCodeSystem + "\" for ValueSet: "
|
||||
|
@ -178,8 +179,9 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
|
||||
case UCUM_VALUESET_URL: {
|
||||
String system = theCodeSystem;
|
||||
expectSystem = UCUM_CODESYSTEM_URL;
|
||||
if (system == null && theOptions.isInferSystem()) {
|
||||
system = UCUM_CODESYSTEM_URL;
|
||||
system = expectSystem;
|
||||
}
|
||||
CodeValidationResult validationResult =
|
||||
validateLookupCode(theValidationSupportContext, theCode, system);
|
||||
|
@ -197,9 +199,11 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
}
|
||||
}
|
||||
|
||||
return new CodeValidationResult()
|
||||
.setSeverity(IssueSeverity.ERROR)
|
||||
.setMessage("Code \"" + theCode + "\" is not in system: " + USPS_CODESYSTEM_URL);
|
||||
String actualSystem = defaultIfBlank(theCodeSystem, expectSystem);
|
||||
String unknownCodeMessage = myFhirContext
|
||||
.getLocalizer()
|
||||
.getMessage("ca.uhn.fhir.jpa.term.TermReadSvcImpl.unknownCodeInSystem", actualSystem, theCode);
|
||||
return new CodeValidationResult().setSeverity(IssueSeverity.ERROR).setMessage(unknownCodeMessage);
|
||||
}
|
||||
|
||||
if (isBlank(theValueSetUrl)) {
|
||||
|
@ -219,6 +223,10 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
validationResult = new CodeValidationResult()
|
||||
.setCode(lookupResult.getSearchedForCode())
|
||||
.setDisplay(lookupResult.getCodeDisplay());
|
||||
} else if (lookupResult.getErrorMessage() != null) {
|
||||
validationResult = new CodeValidationResult()
|
||||
.setSeverity(IssueSeverity.ERROR)
|
||||
.setMessage(lookupResult.getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,6 +275,7 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
retVal.setSearchedForCode(theCode);
|
||||
retVal.setSearchedForSystem(theSystem);
|
||||
retVal.setFound(false);
|
||||
retVal.setErrorMessage("Code '" + theCode + "' is not valid for system: " + theSystem);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
@ -390,20 +399,22 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
|
|||
private LookupCodeResult lookupUcumCode(String theCode) {
|
||||
InputStream input = ClasspathUtil.loadResourceAsStream("/ucum-essence.xml");
|
||||
String outcome = null;
|
||||
try {
|
||||
UcumEssenceService svc = new UcumEssenceService(input);
|
||||
outcome = svc.analyse(theCode);
|
||||
} catch (UcumException e) {
|
||||
ourLog.warn("Failed parse UCUM code: {}", theCode, e);
|
||||
} finally {
|
||||
ClasspathUtil.close(input);
|
||||
}
|
||||
LookupCodeResult retVal = new LookupCodeResult();
|
||||
retVal.setSearchedForCode(theCode);
|
||||
retVal.setSearchedForSystem(UCUM_CODESYSTEM_URL);
|
||||
if (outcome != null) {
|
||||
retVal.setFound(true);
|
||||
retVal.setCodeDisplay(outcome);
|
||||
|
||||
try {
|
||||
UcumEssenceService svc = new UcumEssenceService(input);
|
||||
outcome = svc.analyse(theCode);
|
||||
if (outcome != null) {
|
||||
retVal.setFound(true);
|
||||
retVal.setCodeDisplay(outcome);
|
||||
}
|
||||
} catch (UcumException e) {
|
||||
ourLog.debug("Failed parse UCUM code: {}", theCode, e);
|
||||
retVal.setErrorMessage(e.getMessage());
|
||||
} finally {
|
||||
ClasspathUtil.close(input);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.validation.SingleValidationMessage;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -38,6 +39,9 @@ abstract class BaseValidatorBridge implements IValidatorModule {
|
|||
if (riMessage.getMessageId() != null) {
|
||||
hapiMessage.setMessageId(riMessage.getMessageId());
|
||||
}
|
||||
if (riMessage.sliceText != null && riMessage.sliceText.length > 0) {
|
||||
hapiMessage.setSliceMessages(Arrays.asList(riMessage.sliceText));
|
||||
}
|
||||
theCtx.addValidationMessage(hapiMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,21 +33,21 @@ public class CommonCodeSystemsTerminologyServiceTest {
|
|||
public void testUcum_LookupCode_Good() {
|
||||
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://unitsofmeasure.org", "Cel");
|
||||
assert outcome != null;
|
||||
assertEquals(true, outcome.isFound());
|
||||
assertTrue(outcome.isFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUcum_LookupCode_Good2() {
|
||||
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://unitsofmeasure.org", "kg/m2");
|
||||
assert outcome != null;
|
||||
assertEquals(true, outcome.isFound());
|
||||
assertTrue(outcome.isFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUcum_LookupCode_Bad() {
|
||||
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://unitsofmeasure.org", "AAAAA");
|
||||
assert outcome != null;
|
||||
assertEquals(false, outcome.isFound());
|
||||
assertFalse(outcome.isFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,7 +82,7 @@ public class CommonCodeSystemsTerminologyServiceTest {
|
|||
vs.setUrl("http://hl7.org/fhir/ValueSet/ucum-units");
|
||||
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(newSupport(), newOptions(), "http://unitsofmeasure.org", "mg", null, vs);
|
||||
assert outcome != null;
|
||||
assertEquals(true, outcome.isOk());
|
||||
assertTrue(outcome.isOk());
|
||||
assertEquals("(milligram)", outcome.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ public class CommonCodeSystemsTerminologyServiceTest {
|
|||
vs.setUrl("http://hl7.org/fhir/ValueSet/ucum-units");
|
||||
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(newSupport(), newOptions().setInferSystem(true), null, "mg", null, vs);
|
||||
assert outcome != null;
|
||||
assertEquals(true, outcome.isOk());
|
||||
assertTrue(outcome.isOk());
|
||||
assertEquals("(milligram)", outcome.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,10 @@ public class CommonCodeSystemsTerminologyServiceTest {
|
|||
ValueSet vs = new ValueSet();
|
||||
vs.setUrl("http://hl7.org/fhir/ValueSet/ucum-units");
|
||||
IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(newSupport(), newOptions(), "http://unitsofmeasure.org", "aaaaa", null, vs);
|
||||
assertNull(outcome);
|
||||
assertNotNull(outcome);
|
||||
assertFalse(outcome.isOk());
|
||||
assertEquals("Error processing unit 'aaaaa': The unit 'aaaaa' is unknown' at position 0", outcome.getMessage());
|
||||
assertEquals("error", outcome.getSeverityCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -207,7 +210,7 @@ public class CommonCodeSystemsTerminologyServiceTest {
|
|||
public void testFetchCodeSystemBuiltIn_Iso3166_DSTU2() {
|
||||
CommonCodeSystemsTerminologyService svc = new CommonCodeSystemsTerminologyService(FhirContext.forDstu2Cached());
|
||||
IBaseResource cs = svc.fetchCodeSystem(CommonCodeSystemsTerminologyService.COUNTRIES_CODESYSTEM_URL);
|
||||
assertEquals(null, cs);
|
||||
assertNull(cs);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -220,7 +223,7 @@ public class CommonCodeSystemsTerminologyServiceTest {
|
|||
@Test
|
||||
public void testFetchCodeSystemBuiltIn_Unknown() {
|
||||
CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem("http://foo");
|
||||
assertEquals(null, cs);
|
||||
assertNull(cs);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -16,9 +16,11 @@ import ca.uhn.fhir.validation.FhirValidator;
|
|||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import ca.uhn.fhir.validation.SingleValidationMessage;
|
||||
import ca.uhn.fhir.validation.ValidationResult;
|
||||
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
|
||||
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
|
||||
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
|
||||
|
@ -26,6 +28,7 @@ import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport
|
|||
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
|
||||
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
|
||||
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
||||
import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.conformance.ProfileUtilities;
|
||||
|
@ -43,6 +46,7 @@ import org.hl7.fhir.r4.model.Consent;
|
|||
import org.hl7.fhir.r4.model.ContactPoint;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.IntegerType;
|
||||
import org.hl7.fhir.r4.model.Media;
|
||||
import org.hl7.fhir.r4.model.Narrative;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
|
@ -51,7 +55,9 @@ import org.hl7.fhir.r4.model.OperationOutcome;
|
|||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Period;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.PrimitiveType;
|
||||
import org.hl7.fhir.r4.model.Procedure;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.RelatedPerson;
|
||||
|
@ -62,6 +68,7 @@ import org.hl7.fhir.r4.model.ValueSet;
|
|||
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
|
||||
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
|
||||
import org.hl7.fhir.r4.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.elementmodel.JsonParser;
|
||||
import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags;
|
||||
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
|
||||
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
|
||||
|
@ -98,6 +105,7 @@ import static org.hamcrest.Matchers.hasSize;
|
|||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.CURRENCIES_CODESYSTEM_URL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -108,6 +116,7 @@ import static org.mockito.Mockito.mock;
|
|||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
public class FhirInstanceValidatorR4Test extends BaseTest {
|
||||
|
||||
|
@ -923,7 +932,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
ourLog.debug(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
||||
assertEquals("Unrecognized property 'foo'", operationOutcome.getIssue().get(0).getDiagnostics());
|
||||
assertEquals("Patient", operationOutcome.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line 5, Col 23", operationOutcome.getIssue().get(0).getLocation().get(1).getValue());
|
||||
assertEquals("Line[5] Col[23]", operationOutcome.getIssue().get(0).getLocation().get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1314,6 +1323,44 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSliceValidation() throws IOException {
|
||||
Patient patient = new Patient();
|
||||
patient.getText().setDiv(new XhtmlNode().setValue("<div></div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||
|
||||
Observation input = new Observation();
|
||||
input.setSubject(new Reference("Patient/A"));
|
||||
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||
input.setStatus(ObservationStatus.FINAL);
|
||||
input.getCode().addCoding().setSystem("http://acme.org").setCode("12345");
|
||||
|
||||
addValidConcept("http://acme.org", "12345");
|
||||
|
||||
IValidationPolicyAdvisor policy = mock(IValidationPolicyAdvisor.class, withSettings().verboseLogging());
|
||||
when(policy.policyForReference(any(), any(), any(), any())).thenReturn(ReferenceValidationPolicy.CHECK_VALID);
|
||||
myInstanceVal.setValidatorPolicyAdvisor(policy);
|
||||
|
||||
IValidatorResourceFetcher fetcher = mock(IValidatorResourceFetcher.class, withSettings().verboseLogging());
|
||||
when(fetcher.fetch(any(), any(), any())).thenAnswer(t -> {
|
||||
return new JsonParser(new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), new VersionCanonicalizer(ourCtx)))
|
||||
.parse(ourCtx.newJsonParser().encodeResourceToString(patient), "Patient");
|
||||
});
|
||||
myInstanceVal.setValidatorResourceFetcher(fetcher);
|
||||
myInstanceVal.setValidationSupport(myValidationSupport);
|
||||
myInstanceVal.setBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore);
|
||||
|
||||
ValidationResult output = myFhirValidator.validateWithResult(input);
|
||||
OperationOutcome oo = (OperationOutcome) output.toOperationOutcome();
|
||||
ourLog.info("Outcome: {}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||
assertEquals(1, oo.getIssue().size());
|
||||
assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode());
|
||||
assertEquals(OperationOutcome.IssueSeverity.INFORMATION, oo.getIssue().get(0).getSeverity());
|
||||
assertEquals("Details for Patient/A matching against profile http://hl7.org/fhir/StructureDefinition/Patient|4.0.1 - Observation.subject->Patient.text: Narrative.div: minimum required = 1, but only found 0 (from http://hl7.org/fhir/StructureDefinition/Narrative|4.0.1)", oo.getIssue().get(0).getDiagnostics());
|
||||
assertThat(oo.getIssue().get(0).getLocation().stream().map(PrimitiveType::getValue).collect(Collectors.toList()), Matchers.contains(
|
||||
"Observation.subject", "Line[1] Col[238]"
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateResourceWithExampleBindingCodeValidationFailingNonLoinc() {
|
||||
Observation input = new Observation();
|
||||
|
@ -1416,7 +1463,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
output = myFhirValidator.validateWithResult(input);
|
||||
all = logResultsAndReturnNonInformationalOnes(output);
|
||||
assertEquals(2, all.size());
|
||||
assertThat(all.get(0).getMessage(), containsString("Validation failed for 'http://unitsofmeasure.org#Heck'"));
|
||||
assertThat(all.get(0).getMessage(), containsString("The unit 'Heck' is unknown' at position 0 for 'http://unitsofmeasure.org#Heck'"));
|
||||
assertThat(all.get(1).getMessage(), containsString("The value provided ('Heck') is not in the value set 'Body Temperature Units'"));
|
||||
|
||||
}
|
||||
|
@ -1518,7 +1565,8 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
ValidationResult output = myFhirValidator.validateWithResult(input);
|
||||
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
|
||||
assertEquals(1, errors.size(), errors.toString());
|
||||
assertThat(errors.get(0).getMessage(), containsString("The value provided ('BLAH') is not in the value set 'CurrencyCode' (http://hl7.org/fhir/ValueSet/currencies|4.0.1), and a code is required from this value set) (error message = Unknown code 'BLAH' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/currencies')"));
|
||||
assertThat(errors.get(0).getMessage(), containsString("The value provided ('BLAH') is not in the value set 'CurrencyCode' (http://hl7.org/fhir/ValueSet/currencies|4.0.1)"));
|
||||
assertThat(errors.get(0).getMessage(), containsString("error message = Unknown code \"urn:iso:std:iso:4217#BLAH\""));
|
||||
|
||||
|
||||
}
|
||||
|
@ -1602,6 +1650,90 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testValidateCommonCodes_Ucum_ErrorMessageIsPreserved() {
|
||||
buildValidationSupportWithLogicalAndSupport(false);
|
||||
|
||||
Observation input = new Observation();
|
||||
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||
input.setStatus(ObservationStatus.AMENDED);
|
||||
input.getCode().addCoding().setSystem("http://loinc.org").setCode("1234").setDisplay("FOO");
|
||||
input.setValue(new Quantity(
|
||||
null,
|
||||
123,
|
||||
"http://unitsofmeasure.org",
|
||||
"MG/DL",
|
||||
"MG/DL"
|
||||
));
|
||||
String inputString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
|
||||
ourLog.info("Input:\n{}", inputString);
|
||||
|
||||
ValidationResult result = myFhirValidator.validateWithResult(inputString);
|
||||
|
||||
OperationOutcome oo = (OperationOutcome) result.toOperationOutcome();
|
||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||
|
||||
assertEquals("Error processing unit 'MG/DL': The unit 'DL' is unknown' at position 3 for 'http://unitsofmeasure.org#MG/DL'", result.getMessages().get(0).getMessage());
|
||||
assertEquals(ResultSeverityEnum.ERROR, result.getMessages().get(0).getSeverity());
|
||||
assertEquals(15, result.getMessages().get(0).getLocationLine());
|
||||
assertEquals(4, result.getMessages().get(0).getLocationCol());
|
||||
assertEquals("Observation.value.ofType(Quantity)", result.getMessages().get(0).getLocationString());
|
||||
assertEquals("Terminology_PassThrough_TX_Message", result.getMessages().get(0).getMessageId());
|
||||
|
||||
assertEquals(15, ((IntegerType) oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue());
|
||||
assertEquals(4, ((IntegerType) oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue());
|
||||
assertEquals("Terminology_PassThrough_TX_Message", ((StringType) oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue());
|
||||
assertEquals("Error processing unit 'MG/DL': The unit 'DL' is unknown' at position 3 for 'http://unitsofmeasure.org#MG/DL'", oo.getIssue().get(0).getDiagnostics());
|
||||
assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode());
|
||||
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||
assertEquals(2, oo.getIssue().get(0).getLocation().size());
|
||||
assertEquals("Observation.value.ofType(Quantity)", oo.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line[15] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateCommonCodes_Currency_ErrorMessageIsPreserved() {
|
||||
buildValidationSupportWithLogicalAndSupport(false);
|
||||
|
||||
Observation input = new Observation();
|
||||
input.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||
input.setStatus(ObservationStatus.AMENDED);
|
||||
input.getCode().addCoding().setSystem("http://loinc.org").setCode("1234").setDisplay("FOO");
|
||||
input.setValue(new Quantity(
|
||||
null,
|
||||
123,
|
||||
CURRENCIES_CODESYSTEM_URL,
|
||||
"blah",
|
||||
"blah"
|
||||
));
|
||||
String inputString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
|
||||
ourLog.info("Results:\n{}", inputString);
|
||||
|
||||
ValidationResult result = myFhirValidator.validateWithResult(inputString);
|
||||
|
||||
OperationOutcome oo = (OperationOutcome) result.toOperationOutcome();
|
||||
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||
|
||||
assertEquals("Unknown code 'urn:iso:std:iso:4217#blah' for 'urn:iso:std:iso:4217#blah'", result.getMessages().get(0).getMessage());
|
||||
assertEquals(ResultSeverityEnum.ERROR, result.getMessages().get(0).getSeverity());
|
||||
assertEquals(15, result.getMessages().get(0).getLocationLine());
|
||||
assertEquals(4, result.getMessages().get(0).getLocationCol());
|
||||
assertEquals("Observation.value.ofType(Quantity)", result.getMessages().get(0).getLocationString());
|
||||
assertEquals("Terminology_PassThrough_TX_Message", result.getMessages().get(0).getMessageId());
|
||||
|
||||
assertEquals(15, ((IntegerType) oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue());
|
||||
assertEquals(4, ((IntegerType) oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue());
|
||||
assertEquals("Terminology_PassThrough_TX_Message", ((StringType) oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue());
|
||||
assertEquals("Unknown code 'urn:iso:std:iso:4217#blah' for 'urn:iso:std:iso:4217#blah'", oo.getIssue().get(0).getDiagnostics());
|
||||
assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode());
|
||||
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity());
|
||||
assertEquals(2, oo.getIssue().get(0).getLocation().size());
|
||||
assertEquals("Observation.value.ofType(Quantity)", oo.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line[15] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPatientSingleCommunicationLanguage_en() throws IOException {
|
||||
final String encoded = loadResource("patient-with-single-comm-lang-en.json");
|
||||
|
@ -1678,6 +1810,25 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void buildValidationSupportWithLogicalAndSupport(boolean theLogicalAnd) {
|
||||
myFhirValidator = ourCtx.newValidator();
|
||||
myFhirValidator.setValidateAgainstStandardSchema(false);
|
||||
myFhirValidator.setValidateAgainstStandardSchematron(false);
|
||||
// This is only used if the validation is performed with validationOptions.isConcurrentBundleValidation = true
|
||||
myFhirValidator.setExecutorService(Executors.newFixedThreadPool(4));
|
||||
|
||||
myMockSupport = mock(IValidationSupport.class);
|
||||
when(myMockSupport.getFhirContext()).thenReturn(ourCtx);
|
||||
ValidationSupportChain chain = new ValidationSupportChain(
|
||||
myDefaultValidationSupport,
|
||||
myMockSupport,
|
||||
new CommonCodeSystemsTerminologyService(ourCtx),
|
||||
new InMemoryTerminologyServerValidationSupport(ourCtx),
|
||||
new SnapshotGeneratingValidationSupport(ourCtx));
|
||||
myValidationSupport = new CachingValidationSupport(chain, theLogicalAnd);
|
||||
myInstanceVal = new FhirInstanceValidator(myValidationSupport);
|
||||
myFhirValidator.registerValidatorModule(myInstanceVal);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void verifyFormatParsersNotLoaded() throws Exception {
|
||||
|
@ -1695,7 +1846,6 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
assertFalse(ClassesLoadedFlags.ourXmlParserBaseLoaded);
|
||||
}
|
||||
|
||||
|
||||
@AfterAll
|
||||
public static void afterClassClearContext() throws IOException, NoSuchFieldException {
|
||||
myDefaultValidationSupport.flush();
|
||||
|
@ -1703,19 +1853,4 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
|
|||
TestUtil.randomizeLocaleAndTimezone();
|
||||
}
|
||||
|
||||
private void buildValidationSupportWithLogicalAndSupport(boolean theLogicalAnd) {
|
||||
myFhirValidator = ourCtx.newValidator();
|
||||
myFhirValidator.setValidateAgainstStandardSchema(false);
|
||||
myFhirValidator.setValidateAgainstStandardSchematron(false);
|
||||
// This is only used if the validation is performed with validationOptions.isConcurrentBundleValidation = true
|
||||
myFhirValidator.setExecutorService(Executors.newFixedThreadPool(4));
|
||||
|
||||
myMockSupport = mock(IValidationSupport.class);
|
||||
when(myMockSupport.getFhirContext()).thenReturn(ourCtx);
|
||||
ValidationSupportChain chain = new ValidationSupportChain(myDefaultValidationSupport, myMockSupport, new InMemoryTerminologyServerValidationSupport(ourCtx), new CommonCodeSystemsTerminologyService(ourCtx), new SnapshotGeneratingValidationSupport(ourCtx));
|
||||
myValidationSupport = new CachingValidationSupport(chain, theLogicalAnd);
|
||||
myInstanceVal = new FhirInstanceValidator(myValidationSupport);
|
||||
myFhirValidator.registerValidatorModule(myInstanceVal);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -868,7 +868,7 @@ public class FhirInstanceValidatorR4BTest extends BaseTest {
|
|||
ourLog.debug(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
||||
assertEquals("Unrecognized property 'foo'", operationOutcome.getIssue().get(0).getDiagnostics());
|
||||
assertEquals("Patient", operationOutcome.getIssue().get(0).getLocation().get(0).getValue());
|
||||
assertEquals("Line 5, Col 23", operationOutcome.getIssue().get(0).getLocation().get(1).getValue());
|
||||
assertEquals("Line[5] Col[23]", operationOutcome.getIssue().get(0).getLocation().get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1329,7 +1329,7 @@ public class FhirInstanceValidatorR4BTest extends BaseTest {
|
|||
output = myFhirValidator.validateWithResult(input);
|
||||
all = logResultsAndReturnNonInformationalOnes(output);
|
||||
assertEquals(2, all.size());
|
||||
assertThat(all.get(0).getMessage(), containsString("Validation failed for 'http://unitsofmeasure.org#Heck'"));
|
||||
assertThat(all.get(0).getMessage(), containsString("The unit 'Heck' is unknown' at position 0 for 'http://unitsofmeasure.org#Heck'"));
|
||||
assertThat(all.get(1).getMessage(), containsString("The value provided ('Heck') is not in the value set 'Body Temperature Units'"));
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue