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:
James Agnew 2023-09-07 08:52:38 -04:00 committed by GitHub
parent 3e46f71542
commit a676506fc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 468 additions and 158 deletions

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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) {
@ -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");
}

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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 "));
}

View File

@ -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,21 +399,23 @@ 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);
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;
}

View File

@ -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);
}
}

View File

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

View File

@ -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);
}
}

View File

@ -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'"));
}