From 3f042fdc6d94595ac01f2b881b6277e80e9f58ed Mon Sep 17 00:00:00 2001 From: Martha Mitran Date: Tue, 5 Nov 2024 13:15:57 -0800 Subject: [PATCH] Fixes for the translation of parameter issues as part of the output for $validate-code operation. (#6438) * Move the validation providers to the test utilities package such that they can be reused. * Update IValidationSupport.CodeValidationIssue structure such that it meets the FHIR specification. Update RemoteTerminologyServiceValidationSupport and VersionSpecificWorkerContextWrapper translation for issues. * New tests for issue translation. Move test class to a different package so that we can add another test class. * Some simplification in the issue API to simplify building of issues. * Fix compilation errors. * Update providers to allow multiple responses and add support for the fetchCodeSystem call through method find. * Setup the first test for resource validation with remote terminology providers. * Fix NullPointerException * avoid calling validateCode for CodeSystem where system is null * Keep old public API methods (and class name) in IValidationSupport and mark them as deprecated to avoid breaking dependencies. * Revert local change to debug for duplicate errors. * Add more test cases for resource validation. Throw exception to signal missing test setup to make it obvious. * Simplify test setup. * Add some more javadoc * Add javadoc for the new test class * Add more tests * Address code review comments in IValidationSupport. * Add changelog --- .../context/support/IValidationSupport.java | 303 +++++++++++++++--- .../6422-fixes-remote-terminology-issues.yaml | 7 + .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 5 +- ...idateCodeWithRemoteTerminologyR4Test.java} | 167 +++------- .../ValidateWithRemoteTerminologyTest.java | 261 +++++++++++++++ .../encounter/profile-encounter-custom.json | 49 +++ ...idateCode-CodeSystem-encounter-status.json | 59 ++++ .../validateCode-CodeSystem-v2-0203.json | 25 ++ .../validateCode-CodeSystem-v3-ActCode.json | 46 +++ ...alidateCode-ValueSet-encounter-status.json | 59 ++++ ...validateCode-ValueSet-identifier-type.json | 52 +++ ...dateCode-ValueSet-v3-ActEncounterCode.json | 59 ++++ .../validateCode-CodeSystem-ICD9CM.json | 48 +++ ...ateCode-CodeSystem-observation-status.json | 25 ++ .../validateCode-ValueSet-codes.json | 48 +++ ...idateCode-ValueSet-observation-status.json | 25 ++ .../procedure/profile-procedure-slicing.json | 79 +++++ .../procedure/profile-procedure.json | 50 +++ ...dateCode-CodeSystem-absent-or-unknown.json | 46 +++ .../validateCode-CodeSystem-event-status.json | 59 ++++ ...alidateCode-CodeSystem-snomed-invalid.json | 48 +++ .../validateCode-CodeSystem-snomed-valid.json | 25 ++ ...-ValueSet-absent-or-unknown-procedure.json | 59 ++++ .../validateCode-ValueSet-event-status.json | 25 ++ ...ValueSet-procedure-code-invalid-slice.json | 48 +++ ...eCode-ValueSet-procedure-code-invalid.json | 67 ++++ ...ateCode-ValueSet-procedure-code-valid.json | 59 ++++ .../validation/IValidationProviders.java | 104 ++++++ .../validation/IValidationProvidersDstu3.java | 118 +++++++ .../validation/IValidationProvidersR4.java | 112 ++----- .../CommonCodeSystemsTerminologyService.java | 2 +- ...oryTerminologyServerValidationSupport.java | 35 +- ...teTerminologyServiceValidationSupport.java | 36 ++- ...ownCodeSystemWarningValidationSupport.java | 2 +- .../support/ValidationSupportUtils.java | 11 + .../VersionSpecificWorkerContextWrapper.java | 111 +++---- .../hapi/validation/ILookupCodeTest.java | 15 +- .../IRemoteTerminologyLookupCodeTest.java | 1 + .../IRemoteTerminologyValidateCodeTest.java | 59 ++++ .../hapi/validation/IValidateCodeTest.java | 146 +++++---- .../hapi/validation/IValidationProviders.java | 39 --- ...rsionSpecificWorkerContextWrapperTest.java | 31 +- .../FhirInstanceValidatorDstu2Test.java | 8 +- .../FhirInstanceValidatorDstu3Test.java | 5 +- .../IValidateCodeProvidersDstu3.java | 159 --------- ...estionnaireResponseValidatorDstu3Test.java | 9 +- .../RemoteTerminologyLookupCodeDstu3Test.java | 16 +- ...gyLookupCodeWithResponseFileDstu3Test.java | 22 +- ...emoteTerminologyValidateCodeDstu3Test.java | 52 +-- .../FhirInstanceValidatorR4Test.java | 4 +- .../RemoteTerminologyLookupCodeR4Test.java | 21 +- ...ologyLookupCodeWithResponseFileR4Test.java | 22 +- .../RemoteTerminologyValidateCodeR4Test.java | 101 +++--- .../FhirInstanceValidatorR4BTest.java | 4 +- .../FhirInstanceValidatorR5Test.java | 5 +- ...nOutcome-ValueSet-custom-issue-detail.json | 22 ++ 56 files changed, 2300 insertions(+), 775 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml rename hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/{provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java => validation/ValidateCodeWithRemoteTerminologyR4Test.java} (59%) create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java rename hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java => hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java (55%) delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java create mode 100644 hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index 71d561c1db3d..fdbc4ca31d77 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -440,74 +440,259 @@ default String getName() { return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support"; } + /** + * Defines codes in system http://hl7.org/fhir/issue-severity. + */ + /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */ enum IssueSeverity { /** * The issue caused the action to fail, and no further checking could be performed. */ - FATAL, + FATAL("fatal"), /** * The issue is sufficiently important to cause the action to fail. */ - ERROR, + ERROR("error"), /** * The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired. */ - WARNING, + WARNING("warning"), /** * The issue has no relation to the degree of success of the action. */ - INFORMATION + INFORMATION("information"), + /** + * The operation was successful. + */ + SUCCESS("success"); + // the spec for OperationOutcome mentions that a code from http://hl7.org/fhir/issue-severity is required + + private final String myCode; + + IssueSeverity(String theCode) { + myCode = theCode; + } + /** + * Provide mapping to a code in system http://hl7.org/fhir/issue-severity. + * @return the code + */ + public String getCode() { + return myCode; + } + /** + * Creates a {@link IssueSeverity} object from the given code. + * @return the {@link IssueSeverity} + */ + public static IssueSeverity fromCode(String theCode) { + switch (theCode) { + case "fatal": + return FATAL; + case "error": + return ERROR; + case "warning": + return WARNING; + case "information": + return INFORMATION; + case "success": + return SUCCESS; + default: + return null; + } + } } - enum CodeValidationIssueCode { - NOT_FOUND, - CODE_INVALID, - INVALID, - OTHER + /** + * Defines codes in system http://hl7.org/fhir/issue-type. + * The binding is enforced as a part of validation logic in the FHIR Core Validation library where an exception is thrown. + * Only a sub-set of these codes are defined as constants because they relate to validation, + * If there are additional ones that come up, for Remote Terminology they are currently supported via + * {@link IValidationSupport.CodeValidationIssue#CodeValidationIssue(String, IssueSeverity, String)} + * while for internal validators, more constants can be added to make things easier and consistent. + * This maps to resource OperationOutcome.issue.code. + */ + /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */ + class CodeValidationIssueCode { + public static final CodeValidationIssueCode NOT_FOUND = new CodeValidationIssueCode("not-found"); + public static final CodeValidationIssueCode CODE_INVALID = new CodeValidationIssueCode("code-invalid"); + public static final CodeValidationIssueCode INVALID = new CodeValidationIssueCode("invalid"); + + private final String myCode; + + // this is intentionally not exposed + CodeValidationIssueCode(String theCode) { + myCode = theCode; + } + + /** + * Retrieve the corresponding code from system http://hl7.org/fhir/issue-type. + * @return the code + */ + public String getCode() { + return myCode; + } } - enum CodeValidationIssueCoding { - VS_INVALID, - NOT_FOUND, - NOT_IN_VS, + /** + * Holds information about the details of a {@link CodeValidationIssue}. + * This maps to resource OperationOutcome.issue.details. + */ + /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */ + class CodeValidationIssueDetails { + private final String myText; + private List myCodings; + + public CodeValidationIssueDetails(String theText) { + myText = theText; + } + + // intentionally not exposed + void addCoding(CodeValidationIssueCoding theCoding) { + getCodings().add(theCoding); + } - INVALID_CODE, - INVALID_DISPLAY, - OTHER + public CodeValidationIssueDetails addCoding(String theSystem, String theCode) { + if (myCodings == null) { + myCodings = new ArrayList<>(); + } + myCodings.add(new CodeValidationIssueCoding(theSystem, theCode)); + return this; + } + + public String getText() { + return myText; + } + + public List getCodings() { + if (myCodings == null) { + myCodings = new ArrayList<>(); + } + return myCodings; + } } - class CodeValidationIssue { + /** + * Defines codes that can be part of the details of an issue. + * There are some constants available (pre-defined) for codes for system http://hl7.org/fhir/tools/CodeSystem/tx-issue-type. + * This maps to resource OperationOutcome.issue.details.coding[0].code. + */ + class CodeValidationIssueCoding { + public static String TX_ISSUE_SYSTEM = "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type"; + public static CodeValidationIssueCoding VS_INVALID = + new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-invalid"); + public static final CodeValidationIssueCoding NOT_FOUND = + new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-found"); + public static final CodeValidationIssueCoding NOT_IN_VS = + new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-in-vs"); + public static final CodeValidationIssueCoding INVALID_CODE = + new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "invalid-code"); + public static final CodeValidationIssueCoding INVALID_DISPLAY = + new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-display"); + private final String mySystem, myCode; + + // this is intentionally not exposed + CodeValidationIssueCoding(String theSystem, String theCode) { + mySystem = theSystem; + myCode = theCode; + } + + /** + * Retrieve the corresponding code for the details of a validation issue. + * @return the code + */ + public String getCode() { + return myCode; + } + + /** + * Retrieve the system for the details of a validation issue. + * @return the system + */ + public String getSystem() { + return mySystem; + } + } - private final String myMessage; + /** + * This is a hapi-fhir internal version agnostic object holding information about a validation issue. + * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead. + */ + class CodeValidationIssue { + private final String myDiagnostics; private final IssueSeverity mySeverity; private final CodeValidationIssueCode myCode; - private final CodeValidationIssueCoding myCoding; + private CodeValidationIssueDetails myDetails; public CodeValidationIssue( - String theMessage, - IssueSeverity mySeverity, - CodeValidationIssueCode theCode, - CodeValidationIssueCoding theCoding) { - this.myMessage = theMessage; - this.mySeverity = mySeverity; - this.myCode = theCode; - this.myCoding = theCoding; + String theDiagnostics, IssueSeverity theSeverity, CodeValidationIssueCode theTypeCode) { + this(theDiagnostics, theSeverity, theTypeCode, null); + } + + public CodeValidationIssue(String theDiagnostics, IssueSeverity theSeverity, String theTypeCode) { + this(theDiagnostics, theSeverity, new CodeValidationIssueCode(theTypeCode), null); } + public CodeValidationIssue( + String theDiagnostics, + IssueSeverity theSeverity, + CodeValidationIssueCode theType, + CodeValidationIssueCoding theDetailsCoding) { + myDiagnostics = theDiagnostics; + mySeverity = theSeverity; + myCode = theType; + // reuse the diagnostics message as a detail text message + myDetails = new CodeValidationIssueDetails(theDiagnostics); + myDetails.addCoding(theDetailsCoding); + } + + /** + * @deprecated Please use {@link #getDiagnostics()} instead. + */ + @Deprecated(since = "7.4.6") public String getMessage() { - return myMessage; + return getDiagnostics(); + } + + public String getDiagnostics() { + return myDiagnostics; } public IssueSeverity getSeverity() { return mySeverity; } + /** + * @deprecated Please use {@link #getType()} instead. + */ + @Deprecated(since = "7.4.6") public CodeValidationIssueCode getCode() { + return getType(); + } + + public CodeValidationIssueCode getType() { return myCode; } + /** + * @deprecated Please use {@link #getDetails()} instead. That has support for multiple codings. + */ + @Deprecated(since = "7.4.6") public CodeValidationIssueCoding getCoding() { - return myCoding; + return myDetails != null + ? myDetails.getCodings().stream().findFirst().orElse(null) + : null; + } + + public void setDetails(CodeValidationIssueDetails theDetails) { + this.myDetails = theDetails; + } + + public CodeValidationIssueDetails getDetails() { + return myDetails; + } + + public boolean hasIssueDetailCode(@Nonnull String theCode) { + // this method is system agnostic at the moment but it can be restricted if needed + return myDetails.getCodings().stream().anyMatch(coding -> theCode.equals(coding.getCode())); } } @@ -671,6 +856,10 @@ public String getType() { } } + /** + * This is a hapi-fhir internal version agnostic object holding information about the validation result. + * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult. + */ class CodeValidationResult { public static final String SOURCE_DETAILS = "sourceDetails"; public static final String RESULT = "result"; @@ -686,7 +875,7 @@ class CodeValidationResult { private String myDisplay; private String mySourceDetails; - private List myCodeValidationIssues; + private List myIssues; public CodeValidationResult() { super(); @@ -771,23 +960,48 @@ public CodeValidationResult setSeverity(IssueSeverity theSeverity) { return this; } + /** + * @deprecated Please use method {@link #getIssues()} instead. + */ + @Deprecated(since = "7.4.6") public List getCodeValidationIssues() { - if (myCodeValidationIssues == null) { - myCodeValidationIssues = new ArrayList<>(); - } - return myCodeValidationIssues; + return getIssues(); } + /** + * @deprecated Please use method {@link #setIssues(List)} instead. + */ + @Deprecated(since = "7.4.6") public CodeValidationResult setCodeValidationIssues(List theCodeValidationIssues) { - myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues); - return this; + return setIssues(theCodeValidationIssues); } + /** + * @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead. + */ + @Deprecated(since = "7.4.6") public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) { getCodeValidationIssues().add(theCodeValidationIssue); return this; } + public List getIssues() { + if (myIssues == null) { + myIssues = new ArrayList<>(); + } + return myIssues; + } + + public CodeValidationResult setIssues(List theIssues) { + myIssues = new ArrayList<>(theIssues); + return this; + } + + public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) { + getIssues().add(theCodeValidationIssue); + return this; + } + public boolean isOk() { return isNotBlank(myCode); } @@ -811,17 +1025,19 @@ public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String t public String getSeverityCode() { String retVal = null; if (getSeverity() != null) { - retVal = getSeverity().name().toLowerCase(); + retVal = getSeverity().getCode(); } return retVal; } /** - * Sets an issue severity as a string code. Value must be the name of - * one of the enum values in {@link IssueSeverity}. Value is case-insensitive. + * Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead. + * @param theSeverityCode the code + * @return the current {@link CodeValidationResult} instance */ - public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) { - setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase())); + @Deprecated(since = "7.4.6") + public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) { + setSeverity(IssueSeverity.fromCode(theSeverityCode)); return this; } @@ -838,6 +1054,11 @@ public IBaseParameters toParameters(FhirContext theContext) { if (isNotBlank(getSourceDetails())) { ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails()); } + /* + should translate issues as well, except that is version specific code, so it requires more refactoring + or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult + @see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation + */ return retVal; } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml new file mode 100644 index 000000000000..9b6dc6320ce9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 6422 +title: "Previously, since 7.4.4 the validation issue detail codes were not translated correctly for Remote Terminology +validateCode calls. The detail code used was `invalid-code` for all use-cases which resulted in profile binding strength +not being applied to the issue severity as expected when validating resources against a profile. +This has been fixed and issue detail codes are translated correctly." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index e9f07b798a53..f443a55be83a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -1044,7 +1044,8 @@ private void expandValueSetHandleIncludeOrExclude( if (theExpansionOptions != null && !theExpansionOptions.isFailOnMissingCodeSystem() // Code system is unknown, therefore NOT_FOUND - && e.getCodeValidationIssue().getCoding() == CodeValidationIssueCoding.NOT_FOUND) { + && e.getCodeValidationIssue() + .hasIssueDetailCode(CodeValidationIssueCoding.NOT_FOUND.getCode())) { return; } throw new InternalErrorException(Msg.code(888) + e); @@ -2190,7 +2191,7 @@ private CodeValidationResult createFailureCodeValidationResult( .setSeverity(IssueSeverity.ERROR) .setCodeSystemVersion(theCodeSystemVersion) .setMessage(theMessage) - .addCodeValidationIssue(new CodeValidationIssue( + .addIssue(new CodeValidationIssue( theMessage, IssueSeverity.ERROR, CodeValidationIssueCode.CODE_INVALID, diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateCodeWithRemoteTerminologyR4Test.java similarity index 59% rename from hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java rename to hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateCodeWithRemoteTerminologyR4Test.java index 4da204217f05..3787c611eb75 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateCodeWithRemoteTerminologyR4Test.java @@ -1,29 +1,21 @@ -package ca.uhn.fhir.jpa.provider.r4; +package ca.uhn.fhir.jpa.validation; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.config.JpaConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.param.UriParam; -import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import jakarta.servlet.http.HttpServletRequest; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; -import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.ValueSet; import org.junit.jupiter.api.AfterEach; @@ -33,9 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import java.util.ArrayList; -import java.util.List; - +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,15 +33,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -/* +/** * This set of integration tests that instantiates and injects an instance of * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport} * into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology - * implementation. It also exercises the code found in - * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode} + * implementation. It also exercises the validateCode output translation code found in + * {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport} */ -public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeOperationWithRemoteTerminologyR4Test.class); +public class ValidateCodeWithRemoteTerminologyR4Test extends BaseResourceProviderR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCodeWithRemoteTerminologyR4Test.class); private static final String DISPLAY = "DISPLAY"; private static final String DISPLAY_BODY_MASS_INDEX = "Body mass index (BMI) [Ratio]"; private static final String CODE_BODY_MASS_INDEX = "39156-5"; @@ -64,8 +54,8 @@ public class ValidateCodeOperationWithRemoteTerminologyR4Test extends BaseResour protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private RemoteTerminologyServiceValidationSupport mySvc; - private MyCodeSystemProvider myCodeSystemProvider; - private MyValueSetProvider myValueSetProvider; + private IValidationProviders.MyValidationProvider myCodeSystemProvider; + private IValidationProviders.MyValidationProvider myValueSetProvider; @Autowired @Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN) @@ -76,8 +66,8 @@ public void before() throws Exception { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); myValidationSupportChain.addValidationSupport(0, mySvc); - myCodeSystemProvider = new MyCodeSystemProvider(); - myValueSetProvider = new MyValueSetProvider(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); + myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4(); ourRestfulServerExtension.registerProvider(myCodeSystemProvider); ourRestfulServerExtension.registerProvider(myValueSetProvider); } @@ -103,11 +93,11 @@ public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereSystemIsDiffere @Test public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSystems() { - myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); - myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247")); - myCodeSystemProvider.myReturnParams = new Parameters(); - myCodeSystemProvider.myReturnParams.addParameter("result", true); - myCodeSystemProvider.myReturnParams.addParameter("display", DISPLAY); + final String code = "P"; + final String system = CODE_SYSTEM_V2_0247_URI;; + + Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY); + setupCodeSystemValidateCode(system, code, params); logAllConcepts(); @@ -115,8 +105,8 @@ public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSys .operation() .onType(CodeSystem.class) .named(JpaConstants.OPERATION_VALIDATE_CODE) - .withParameter(Parameters.class, "coding", new Coding().setSystem(CODE_SYSTEM_V2_0247_URI).setCode("P")) - .andParameter("url", new UriType(CODE_SYSTEM_V2_0247_URI)) + .withParameter(Parameters.class, "coding", new Coding().setSystem(system).setCode(code)) + .andParameter("url", new UriType(system)) .execute(); String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); @@ -128,7 +118,7 @@ public void validateCodeOperationOnCodeSystem_byCodingAndUrl_usingBuiltInCodeSys @Test public void validateCodeOperationOnCodeSystem_byCodingAndUrlWhereCodeSystemIsUnknown_returnsFalse() { - myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); + myCodeSystemProvider.setShouldThrowExceptionForResourceNotFound(false); Parameters respParam = myClient .operation() @@ -166,21 +156,21 @@ public void validateCodeOperationOnValueSet_byCodingAndUrlWhereSystemIsDifferent @Test public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSystems() { - myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); - myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); - myValueSetProvider.myReturnValueSets = new ArrayList<>(); - myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); - myValueSetProvider.myReturnParams = new Parameters(); - myValueSetProvider.myReturnParams.addParameter("result", true); - myValueSetProvider.myReturnParams.addParameter("display", DISPLAY); + final String code = "alerts"; + final String system = "http://terminology.hl7.org/CodeSystem/list-example-use-codes"; + final String valueSetUrl = "http://hl7.org/fhir/ValueSet/list-example-codes"; + + Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY); + setupValueSetValidateCode(valueSetUrl, system, code, params); + setupCodeSystemValidateCode(system, code, params); Parameters respParam = myClient .operation() .onType(ValueSet.class) .named(JpaConstants.OPERATION_VALIDATE_CODE) - .withParameter(Parameters.class, "code", new CodeType("alerts")) - .andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/list-example-use-codes")) - .andParameter("url", new UriType("http://hl7.org/fhir/ValueSet/list-example-codes")) + .withParameter(Parameters.class, "code", new CodeType(code)) + .andParameter("system", new UriType(system)) + .andParameter("url", new UriType(valueSetUrl)) .useHttpGet() .execute(); @@ -193,21 +183,20 @@ public void validateCodeOperationOnValueSet_byUrlAndSystem_usingBuiltInCodeSyste @Test public void validateCodeOperationOnValueSet_byUrlSystemAndCode() { - myCodeSystemProvider.myReturnCodeSystems = new ArrayList<>(); - myCodeSystemProvider.myReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes")); - myValueSetProvider.myReturnValueSets = new ArrayList<>(); - myValueSetProvider.myReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes")); - myValueSetProvider.myReturnParams = new Parameters(); - myValueSetProvider.myReturnParams.addParameter("result", true); - myValueSetProvider.myReturnParams.addParameter("display", DISPLAY_BODY_MASS_INDEX); + final String code = CODE_BODY_MASS_INDEX; + final String system = "http://terminology.hl7.org/CodeSystem/list-example-use-codes"; + final String valueSetUrl = "http://hl7.org/fhir/ValueSet/list-example-codes"; + + Parameters params = new Parameters().addParameter("result", true).addParameter("display", DISPLAY_BODY_MASS_INDEX); + setupValueSetValidateCode(valueSetUrl, system, code, params); Parameters respParam = myClient .operation() .onType(ValueSet.class) .named(JpaConstants.OPERATION_VALIDATE_CODE) - .withParameter(Parameters.class, "code", new CodeType(CODE_BODY_MASS_INDEX)) - .andParameter("url", new UriType("https://loinc.org")) - .andParameter("system", new UriType("http://loinc.org")) + .withParameter(Parameters.class, "code", new CodeType(code)) + .andParameter("url", new UriType(valueSetUrl)) + .andParameter("system", new UriType(system)) .execute(); String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); @@ -219,7 +208,7 @@ public void validateCodeOperationOnValueSet_byUrlSystemAndCode() { @Test public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown_returnsFalse() { - myValueSetProvider.myReturnValueSets = new ArrayList<>(); + myValueSetProvider.setShouldThrowExceptionForResourceNotFound(false); Parameters respParam = myClient .operation() @@ -238,70 +227,18 @@ public void validateCodeOperationOnValueSet_byCodingAndUrlWhereValueSetIsUnknown " - Unknown or unusable ValueSet[" + UNKNOWN_VALUE_SYSTEM_URI + "]"); } - @SuppressWarnings("unused") - private static class MyCodeSystemProvider implements IResourceProvider { - private List myReturnCodeSystems; - private Parameters myReturnParams; - - @Operation(name = "validate-code", idempotent = true, returnParameters = { - @OperationParam(name = "result", type = BooleanType.class, min = 1), - @OperationParam(name = "message", type = StringType.class), - @OperationParam(name = "display", type = StringType.class) - }) - public Parameters validateCode( - HttpServletRequest theServletRequest, - @IdParam(optional = true) IdType theId, - @OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl, - @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, - @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay - ) { - return myReturnParams; - } - - @Search - public List find(@RequiredParam(name = "url") UriParam theUrlParam) { - assert myReturnCodeSystems != null; - return myReturnCodeSystems; - } + private void setupValueSetValidateCode(String theUrl, String theSystem, String theCode, IBaseParameters theResponseParams) { + ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl); + myValueSetProvider.addTerminologyResource(theSystem); + myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, theResponseParams); - @Override - public Class getResourceType() { - return CodeSystem.class; - } + // we currently do this because VersionSpecificWorkerContextWrapper has logic to infer the system when missing + // based on the ValueSet by calling ValidationSupportUtils#extractCodeSystemForCode. + valueSet.getCompose().addInclude().setSystem(theSystem); } - @SuppressWarnings("unused") - private static class MyValueSetProvider implements IResourceProvider { - private Parameters myReturnParams; - private List myReturnValueSets; - - @Operation(name = "validate-code", idempotent = true, returnParameters = { - @OperationParam(name = "result", type = BooleanType.class, min = 1), - @OperationParam(name = "message", type = StringType.class), - @OperationParam(name = "display", type = StringType.class) - }) - public Parameters validateCode( - HttpServletRequest theServletRequest, - @IdParam(optional = true) IdType theId, - @OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl, - @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, - @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, - @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, - @OperationParam(name = "valueSet") ValueSet theValueSet - ) { - return myReturnParams; - } - - @Search - public List find(@RequiredParam(name = "url") UriParam theUrlParam) { - assert myReturnValueSets != null; - return myReturnValueSets; - } - - @Override - public Class getResourceType() { - return ValueSet.class; - } - + private void setupCodeSystemValidateCode(String theUrl, String theCode, IBaseParameters theResponseParams) { + CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl); + myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, theResponseParams); } } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java new file mode 100644 index 000000000000..79a656db39c7 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java @@ -0,0 +1,261 @@ +package ca.uhn.fhir.jpa.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.JpaConfig; +import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; +import ca.uhn.fhir.util.ClasspathUtil; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Procedure; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.util.List; + +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests resource validation with Remote Terminology bindings. + * To create a new test, you need to do 3 things: + * (1) the resource profile, if any custom one is needed should be stored in the FHIR repository + * (2) all the CodeSystem and ValueSet terminology resources need to be added to the corresponding resource provider. + * At the moment only placeholder CodeSystem/ValueSet resources are returned with id and url populated. For the moment + * there was no need to load the full resource, but that can be done if there is logic run which requires it. + * This is a minimal setup. + * (3) the Remote Terminology operation responses that are needed for the test need to be added to the corresponding + * resource provider. The intention is to record and use the responses of an actual terminology server + * e.g. OntoServer. + * This is done as a result of the fact that unit test cannot always catch bugs which are introduced as a result of + * changes in the OntoServer or FHIR Validator library, or both. + * @see #setupValueSetValidateCode + * @see #setupCodeSystemValidateCode + * The responses are in Parameters resource format where issues is an OperationOutcome resource. + */ +public class ValidateWithRemoteTerminologyTest extends BaseResourceProviderR4Test { + private static final FhirContext ourCtx = FhirContext.forR4Cached(); + + @RegisterExtension + protected static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); + private RemoteTerminologyServiceValidationSupport mySvc; + @Autowired + @Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN) + private ValidationSupportChain myValidationSupportChain; + private IValidationProviders.MyValidationProvider myCodeSystemProvider; + private IValidationProviders.MyValidationProvider myValueSetProvider; + + @BeforeEach + public void before() { + String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); + mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); + myValidationSupportChain.addValidationSupport(0, mySvc); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); + myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4(); + ourRestfulServerExtension.registerProvider(myCodeSystemProvider); + ourRestfulServerExtension.registerProvider(myValueSetProvider); + } + + @AfterEach + public void after() { + myValidationSupportChain.removeValidationSupport(mySvc); + ourRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors(); + ourRestfulServerExtension.unregisterProvider(myCodeSystemProvider); + ourRestfulServerExtension.unregisterProvider(myValueSetProvider); + } + + @Test + public void validate_withProfileWithValidCodesFromAllBindingTypes_returnsNoErrors() { + // setup + final StructureDefinition profileEncounter = ClasspathUtil.loadResource(ourCtx, StructureDefinition.class, "validation/encounter/profile-encounter-custom.json"); + myClient.update().resource(profileEncounter).execute(); + + final String statusCode = "planned"; + final String classCode = "IMP"; + final String identifierTypeCode = "VN"; + + final String statusSystem = "http://hl7.org/fhir/encounter-status"; // implied system + final String classSystem = "http://terminology.hl7.org/CodeSystem/v3-ActCode"; + final String identifierTypeSystem = "http://terminology.hl7.org/CodeSystem/v2-0203"; + + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/encounter-status", "http://hl7.org/fhir/encounter-status", statusCode, "validation/encounter/validateCode-ValueSet-encounter-status.json"); + setupValueSetValidateCode("http://terminology.hl7.org/ValueSet/v3-ActEncounterCode", "http://terminology.hl7.org/CodeSystem/v3-ActCode", classCode, "validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json"); + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/identifier-type", "http://hl7.org/fhir/identifier-type", identifierTypeCode, "validation/encounter/validateCode-ValueSet-identifier-type.json"); + + setupCodeSystemValidateCode(statusSystem, statusCode, "validation/encounter/validateCode-CodeSystem-encounter-status.json"); + setupCodeSystemValidateCode(classSystem, classCode, "validation/encounter/validateCode-CodeSystem-v3-ActCode.json"); + setupCodeSystemValidateCode(identifierTypeSystem, identifierTypeCode, "validation/encounter/validateCode-CodeSystem-v2-0203.json"); + + Encounter encounter = new Encounter(); + encounter.getMeta().addProfile("http://example.ca/fhir/StructureDefinition/profile-encounter"); + + // required binding + encounter.setStatus(Encounter.EncounterStatus.fromCode(statusCode)); + + // preferred binding + encounter.getClass_() + .setSystem(classSystem) + .setCode(classCode) + .setDisplay("inpatient encounter"); + + // extensible binding + encounter.addIdentifier() + .getType().addCoding() + .setSystem(identifierTypeSystem) + .setCode(identifierTypeCode) + .setDisplay("Visit number"); + + // execute + List errors = getValidationErrors(encounter); + + // verify + assertThat(errors).isEmpty(); + } + + @Test + public void validate_withInvalidCode_returnsErrors() { + // setup + final String statusCode = "final"; + final String code = "10xx"; + + final String statusSystem = "http://hl7.org/fhir/observation-status"; + final String loincSystem = "http://loinc.org"; + final String system = "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM"; + + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-status", statusSystem, statusCode, "validation/observation/validateCode-ValueSet-observation-status.json"); + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/observation-codes", loincSystem, statusCode, "validation/observation/validateCode-ValueSet-codes.json"); + + setupCodeSystemValidateCode(statusSystem, statusCode, "validation/observation/validateCode-CodeSystem-observation-status.json"); + setupCodeSystemValidateCode(system, code, "validation/observation/validateCode-CodeSystem-ICD9CM.json"); + + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.fromCode(statusCode)); + obs.getCode().addCoding().setCode(code).setSystem(system); + + // execute + List errors = getValidationErrors(obs); + assertThat(errors).hasSize(1); + + // verify + assertThat(errors.get(0)) + .contains("Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM"); + } + + @Test + public void validate_withProfileWithInvalidCode_returnsErrors() { + // setup + String profile = "http://example.ca/fhir/StructureDefinition/profile-procedure"; + StructureDefinition profileProcedure = ClasspathUtil.loadResource(myFhirContext, StructureDefinition.class, "validation/procedure/profile-procedure.json"); + myClient.update().resource(profileProcedure).execute(); + + final String statusCode = "completed"; + final String procedureCode1 = "417005"; + final String procedureCode2 = "xx417005"; + + final String statusSystem = "http://hl7.org/fhir/event-status"; + final String snomedSystem = "http://snomed.info/sct"; + + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json"); + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode1, "validation/procedure/validateCode-ValueSet-procedure-code-valid.json"); + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode2, "validation/procedure/validateCode-ValueSet-procedure-code-invalid.json"); + + setupCodeSystemValidateCode(statusSystem, statusCode, "validation/procedure/validateCode-CodeSystem-event-status.json"); + setupCodeSystemValidateCode(snomedSystem, procedureCode1, "validation/procedure/validateCode-CodeSystem-snomed-valid.json"); + setupCodeSystemValidateCode(snomedSystem, procedureCode2, "validation/procedure/validateCode-CodeSystem-snomed-invalid.json"); + + Procedure procedure = new Procedure(); + procedure.setSubject(new Reference("Patient/P1")); + procedure.setStatus(Procedure.ProcedureStatus.fromCode(statusCode)); + procedure.getCode().addCoding().setSystem(snomedSystem).setCode(procedureCode1); + procedure.getCode().addCoding().setSystem(snomedSystem).setCode(procedureCode2); + procedure.getMeta().addProfile(profile); + + // execute + List errors = getValidationErrors(procedure); + // TODO: there is currently some duplication in the errors returned. This needs to be investigated and fixed. + // assertThat(errors).hasSize(1); + + // verify + // note that we're not selecting an explicit versions (using latest) so the message verification does not include it. + assertThat(StringUtils.join("", errors)) + .contains("Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct'") + .doesNotContain("The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code") + .doesNotContain("http://snomed.info/sct#417005"); + } + + @Test + public void validate_withProfileWithSlicingWithValidCode_returnsNoErrors() { + // setup + String profile = "http://example.ca/fhir/StructureDefinition/profile-procedure-with-slicing"; + StructureDefinition profileProcedure = ClasspathUtil.loadResource(myFhirContext, StructureDefinition.class, "validation/procedure/profile-procedure-slicing.json"); + myClient.update().resource(profileProcedure).execute(); + + final String statusCode = "completed"; + final String procedureCode = "no-procedure-info"; + + final String statusSystem = "http://hl7.org/fhir/event-status"; + final String snomedSystem = "http://snomed.info/sct"; + final String absentUnknownSystem = "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips"; + + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/event-status", statusSystem, statusCode, "validation/procedure/validateCode-ValueSet-event-status.json"); + setupValueSetValidateCode("http://hl7.org/fhir/ValueSet/procedure-code", snomedSystem, procedureCode, "validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json"); + setupValueSetValidateCode("http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips", absentUnknownSystem, procedureCode, "validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json"); + + setupCodeSystemValidateCode(statusSystem, statusCode, "validation/procedure/validateCode-CodeSystem-event-status.json"); + setupCodeSystemValidateCode(absentUnknownSystem, procedureCode, "validation/procedure/validateCode-CodeSystem-absent-or-unknown.json"); + + Procedure procedure = new Procedure(); + procedure.setSubject(new Reference("Patient/P1")); + procedure.setStatus(Procedure.ProcedureStatus.fromCode(statusCode)); + procedure.getCode().addCoding().setSystem(absentUnknownSystem).setCode(procedureCode); + procedure.getMeta().addProfile(profile); + + // execute + List errors = getValidationErrors(procedure); + assertThat(errors).hasSize(0); + } + + private void setupValueSetValidateCode(String theUrl, String theSystem, String theCode, String theTerminologyResponseFile) { + ValueSet valueSet = myValueSetProvider.addTerminologyResource(theUrl); + myCodeSystemProvider.addTerminologyResource(theSystem); + myValueSetProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, valueSet.getUrl(), theCode, ourCtx, theTerminologyResponseFile); + + // we currently do this because VersionSpecificWorkerContextWrapper has logic to infer the system when missing + // based on the ValueSet by calling ValidationSupportUtils#extractCodeSystemForCode. + valueSet.getCompose().addInclude().setSystem(theSystem); + + // you will notice each of these calls require also a call to setupCodeSystemValidateCode + // that is necessary because VersionSpecificWorkerContextWrapper#validateCodeInValueSet + // which also attempts a validateCode against the CodeSystem after the validateCode against the ValueSet + } + + private void setupCodeSystemValidateCode(String theUrl, String theCode, String theTerminologyResponseFile) { + CodeSystem codeSystem = myCodeSystemProvider.addTerminologyResource(theUrl); + myCodeSystemProvider.addTerminologyResponse(OPERATION_VALIDATE_CODE, codeSystem.getUrl(), theCode, ourCtx, theTerminologyResponseFile); + } + + private List getValidationErrors(IBaseResource theResource) { + MethodOutcome resultProcedure = myClient.validate().resource(theResource).execute(); + OperationOutcome operationOutcome = (OperationOutcome) resultProcedure.getOperationOutcome(); + return operationOutcome.getIssue().stream() + .filter(issue -> issue.getSeverity() == OperationOutcome.IssueSeverity.ERROR) + .map(OperationOutcome.OperationOutcomeIssueComponent::getDiagnostics) + .toList(); + } +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json new file mode 100644 index 000000000000..a553a61a1c20 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json @@ -0,0 +1,49 @@ +{ + "resourceType": "StructureDefinition", + "id": "profile-encounter", + "url": "http://example.ca/fhir/StructureDefinition/profile-encounter", + "version": "0.11.0", + "name": "EncounterProfile", + "title": "Encounter Profile", + "status": "active", + "date": "2022-10-15T12:00:00+00:00", + "publisher": "Example Organization", + "fhirVersion": "4.0.1", + "kind": "resource", + "abstract": false, + "type": "Encounter", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Encounter", + "derivation": "constraint", + "differential": { + "element": [ + { + "id": "Encounter.identifier.type.coding", + "path": "Encounter.identifier.type.coding", + "min": 1, + "max": "1", + "mustSupport": true + }, + { + "id": "Encounter.identifier.type.coding.system", + "path": "Encounter.identifier.type.coding.system", + "min": 1, + "fixedUri": "http://terminology.hl7.org/CodeSystem/v2-0203", + "mustSupport": true + }, + { + "id": "Encounter.identifier.type.coding.code", + "path": "Encounter.identifier.type.coding.code", + "min": 1, + "fixedCode": "VN", + "mustSupport": true + }, + { + "id": "Encounter.identifier.type.coding.display", + "path": "Encounter.identifier.type.coding.display", + "min": 1, + "fixedString": "Visit number", + "mustSupport": true + } + ] + } +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json new file mode 100644 index 000000000000..2399dc870ec8 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json @@ -0,0 +1,59 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "planned" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/encounter-status" + }, + { + "name": "version", + "valueString": "5.0.0-ballot" + }, + { + "name": "display", + "valueString": "Planned" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot" + } + }, + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to draft CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json new file mode 100644 index 000000000000..10747c14ee38 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json @@ -0,0 +1,25 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "VN" + }, + { + "name": "system", + "valueUri": "http://terminology.hl7.org/CodeSystem/v2-0203" + }, + { + "name": "version", + "valueString": "3.0.0" + }, + { + "name": "display", + "valueString": "Visit number" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json new file mode 100644 index 000000000000..b692847e0fbb --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json @@ -0,0 +1,46 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "IMP" + }, + { + "name": "system", + "valueUri": "http://terminology.hl7.org/CodeSystem/v3-ActCode" + }, + { + "name": "version", + "valueString": "2018-08-12" + }, + { + "name": "display", + "valueString": "inpatient encounter" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to draft CodeSystem http://terminology.hl7.org/CodeSystem/v3-ActCode|2018-08-12" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json new file mode 100644 index 000000000000..2399dc870ec8 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json @@ -0,0 +1,59 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "planned" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/encounter-status" + }, + { + "name": "version", + "valueString": "5.0.0-ballot" + }, + { + "name": "display", + "valueString": "Planned" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot" + } + }, + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to draft CodeSystem http://hl7.org/fhir/encounter-status|5.0.0-ballot" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json new file mode 100644 index 000000000000..b0767dc2f188 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json @@ -0,0 +1,52 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": false + }, + { + "name": "code", + "valueCode": "VN" + }, + { + "name": "system", + "valueUri": "http://terminology.hl7.org/CodeSystem/v2-0203" + }, + { + "name": "version", + "valueString": "3.0.0" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "not-in-vs" + } + ], + "text": "The provided code 'http://terminology.hl7.org/CodeSystem/v2-0203#VN' was not found in the value set 'http://hl7.org/fhir/ValueSet/identifier-type|5.0.0-ballot'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + } + ] + } + }, + { + "name": "message", + "valueString": "The provided code 'http://terminology.hl7.org/CodeSystem/v2-0203#VN' was not found in the value set 'http://hl7.org/fhir/ValueSet/identifier-type|5.0.0-ballot'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json new file mode 100644 index 000000000000..083dbffae43d --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json @@ -0,0 +1,59 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "IMP" + }, + { + "name": "system", + "valueUri": "http://terminology.hl7.org/CodeSystem/v3-ActCode" + }, + { + "name": "version", + "valueString": "2018-08-12" + }, + { + "name": "display", + "valueString": "inpatient encounter" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use ValueSet http://terminology.hl7.org/ValueSet/v3-ActEncounterCode|2014-03-26" + } + }, + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to draft CodeSystem http://terminology.hl7.org/CodeSystem/v3-ActCode|2018-08-12" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json new file mode 100644 index 000000000000..831ac6660fa6 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json @@ -0,0 +1,48 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": false + }, + { + "name": "code", + "valueCode": "10xx" + }, + { + "name": "system", + "valueUri": "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "invalid-code" + } + ], + "text": "Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM' version '0.1.0'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + } + ] + } + }, + { + "name": "message", + "valueString": "Unknown code '10xx' in the CodeSystem 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM' version '0.1.0'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json new file mode 100644 index 000000000000..7914321876c3 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json @@ -0,0 +1,25 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "final" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/observation-status" + }, + { + "name": "version", + "valueString": "5.0.0-ballot" + }, + { + "name": "display", + "valueString": "Final" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json new file mode 100644 index 000000000000..4571362033f0 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json @@ -0,0 +1,48 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": false + }, + { + "name": "code", + "valueCode": "10xx" + }, + { + "name": "system", + "valueUri": "http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "not-in-vs" + } + ], + "text": "The provided code 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM#10xx' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-codes|5.0.0-ballot'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + } + ] + } + }, + { + "name": "message", + "valueString": "The provided code 'http://fhir.infoway-inforoute.ca/io/psca/CodeSystem/ICD9CM#10xx' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-codes|5.0.0-ballot'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json new file mode 100644 index 000000000000..7914321876c3 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json @@ -0,0 +1,25 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "final" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/observation-status" + }, + { + "name": "version", + "valueString": "5.0.0-ballot" + }, + { + "name": "display", + "valueString": "Final" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json new file mode 100644 index 000000000000..8bc05c70cf00 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json @@ -0,0 +1,79 @@ +{ + "resourceType": "StructureDefinition", + "id": "profile-procedure-with-slicing", + "url": "http://example.ca/fhir/StructureDefinition/profile-procedure-with-slicing", + "version": "0.11.0", + "name": "ProcedureProfile", + "title": "Procedure Profile", + "status": "active", + "date": "2022-10-15T12:00:00+00:00", + "publisher": "Example Organization", + "fhirVersion": "4.0.1", + "kind": "resource", + "abstract": false, + "type": "Procedure", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Procedure", + "derivation": "constraint", + "differential": { + "element": [ + { + "id": "Procedure.code.coding", + "path": "Procedure.code.coding", + "slicing": { + "discriminator": [ + { + "type": "pattern", + "path": "$this" + } + ], + "description": "Discriminated by the bound value set", + "rules": "open" + }, + "mustSupport": true, + "binding": { + "strength": "preferred", + "valueSet": "http://hl7.org/fhir/ValueSet/procedure-code" + } + }, + { + "id": "Procedure.code.coding.display.extension:translation", + "path": "Procedure.code.coding.display.extension", + "sliceName": "translation" + }, + { + "id": "Procedure.code.coding.display.extension:translation.extension", + "path": "Procedure.code.coding.display.extension.extension", + "min": 2 + }, + { + "id": "Procedure.code.coding:absentOrUnknownProcedure", + "path": "Procedure.code.coding", + "sliceName": "absentOrUnknownProcedure", + "short": "Optional slice for representing a code for absent problem or for unknown procedure", + "definition": "Code representing the statement \"absent problem\" or the statement \"procedures unknown\"", + "mustSupport": true, + "binding": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName", + "valueString": "absentOrUnknownProcedure" + } + ], + "strength": "required", + "description": "A code to identify absent or unknown procedures", + "valueSet": "http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips" + } + }, + { + "id": "Procedure.code.coding:absentOrUnknownProcedure.display.extension:translation", + "path": "Procedure.code.coding.display.extension", + "sliceName": "translation" + }, + { + "id": "Procedure.code.coding:absentOrUnknownProcedure.display.extension:translation.extension", + "path": "Procedure.code.coding.display.extension.extension", + "min": 2 + } + ] + } +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json new file mode 100644 index 000000000000..5315694dece4 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json @@ -0,0 +1,50 @@ +{ + "resourceType": "StructureDefinition", + "id": "profile-procedure", + "url": "http://example.ca/fhir/StructureDefinition/profile-procedure", + "version": "0.11.0", + "name": "ProcedureProfile", + "title": "Procedure Profile", + "status": "active", + "date": "2022-10-15T12:00:00+00:00", + "publisher": "Example Organization", + "fhirVersion": "4.0.1", + "kind": "resource", + "abstract": false, + "type": "Procedure", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Procedure", + "derivation": "constraint", + "differential": { + "element": [ + { + "id": "Procedure.code.coding", + "path": "Procedure.code.coding", + "slicing": { + "discriminator": [ + { + "type": "pattern", + "path": "$this" + } + ], + "description": "Discriminated by the bound value set", + "rules": "open" + }, + "mustSupport": true, + "binding": { + "strength": "preferred", + "valueSet": "http://hl7.org/fhir/ValueSet/procedure-code" + } + }, + { + "id": "Procedure.code.coding.display.extension:translation", + "path": "Procedure.code.coding.display.extension", + "sliceName": "translation" + }, + { + "id": "Procedure.code.coding.display.extension:translation.extension", + "path": "Procedure.code.coding.display.extension.extension", + "min": 2 + } + ] + } +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json new file mode 100644 index 000000000000..4d7b20f0881d --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json @@ -0,0 +1,46 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "no-procedure-info" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips" + }, + { + "name": "version", + "valueString": "1.1.0" + }, + { + "name": "display", + "valueString": "No information about past history of procedures" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use CodeSystem http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips|1.1.0" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json new file mode 100644 index 000000000000..620624a991e9 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json @@ -0,0 +1,59 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "completed" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/event-status" + }, + { + "name": "version", + "valueString": "5.0.0-ballot" + }, + { + "name": "display", + "valueString": "Completed" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use CodeSystem http://hl7.org/fhir/event-status|5.0.0-ballot" + } + }, + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to experimental CodeSystem http://hl7.org/fhir/event-status|5.0.0-ballot" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json new file mode 100644 index 000000000000..f6a86048d6e8 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json @@ -0,0 +1,48 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": false + }, + { + "name": "code", + "valueCode": "xx417005" + }, + { + "name": "system", + "valueUri": "http://snomed.info/sct" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "invalid-code" + } + ], + "text": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + } + ] + } + }, + { + "name": "message", + "valueString": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json new file mode 100644 index 000000000000..a602bfda9f00 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json @@ -0,0 +1,25 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "417005" + }, + { + "name": "system", + "valueUri": "http://snomed.info/sct" + }, + { + "name": "version", + "valueString": "http://snomed.info/sct/32506021000036107/version/20241031" + }, + { + "name": "display", + "valueString": "Hospital re-admission" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json new file mode 100644 index 000000000000..aaee02a00233 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json @@ -0,0 +1,59 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "no-procedure-info" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips" + }, + { + "name": "version", + "valueString": "1.1.0" + }, + { + "name": "display", + "valueString": "No information about past history of procedures" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use CodeSystem http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips|1.1.0" + } + }, + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to trial-use ValueSet http://hl7.org/fhir/uv/ips/ValueSet/absent-or-unknown-procedures-uv-ips|1.1.0" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json new file mode 100644 index 000000000000..aaad08b83e8e --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json @@ -0,0 +1,25 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "final" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/procedure-status" + }, + { + "name": "version", + "valueString": "5.0.0-ballot" + }, + { + "name": "display", + "valueString": "Final" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json new file mode 100644 index 000000000000..4dcb4791944e --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json @@ -0,0 +1,48 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": false + }, + { + "name": "code", + "valueCode": "no-procedure-info" + }, + { + "name": "system", + "valueUri": "http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "not-in-vs" + } + ], + "text": "The provided code 'http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips#no-procedure-info' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + } + ] + } + }, + { + "name": "message", + "valueString": "The provided code 'http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips#no-procedure-info' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json new file mode 100644 index 000000000000..fac3785fe2d2 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json @@ -0,0 +1,67 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": false + }, + { + "name": "code", + "valueCode": "xx417005" + }, + { + "name": "system", + "valueUri": "http://snomed.info/sct" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "not-in-vs" + } + ], + "text": "The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + }, + { + "severity": "error", + "code": "code-invalid", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "invalid-code" + } + ], + "text": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'" + }, + "location": [ + "code" + ], + "expression": [ + "code" + ] + } + ] + } + }, + { + "name": "message", + "valueString": "Unknown code 'xx417005' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/32506021000036107/version/20241031'; The provided code 'http://snomed.info/sct#xx417005' was not found in the value set 'http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot'" + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json new file mode 100644 index 000000000000..4554379edadf --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json @@ -0,0 +1,59 @@ +{ + "resourceType": "Parameters", + "parameter": [ + { + "name": "result", + "valueBoolean": true + }, + { + "name": "code", + "valueCode": "417005" + }, + { + "name": "system", + "valueUri": "http://snomed.info/sct" + }, + { + "name": "version", + "valueString": "http://snomed.info/sct/32506021000036107/version/20241031" + }, + { + "name": "display", + "valueString": "Hospital re-admission" + }, + { + "name": "issues", + "resource": { + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to draft ValueSet http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot" + } + }, + { + "severity": "information", + "code": "business-rule", + "details": { + "coding": [ + { + "system": "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code": "status-check" + } + ], + "text": "Reference to experimental ValueSet http://hl7.org/fhir/ValueSet/procedure-code|5.0.0-ballot" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java new file mode 100644 index 000000000000..a3c55c044399 --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java @@ -0,0 +1,104 @@ +package ca.uhn.fhir.test.utilities.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.util.ClasspathUtil; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IDomainResource; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface IValidationProviders { + String CODE_SYSTEM = "http://code.system/url"; + String CODE_SYSTEM_VERSION = "1.0.0"; + String CODE_SYSTEM_NAME = "Test Code System"; + String CODE = "CODE"; + String VALUE_SET_URL = "http://value.set/url"; + String DISPLAY = "Explanation for code TestCode."; + String LANGUAGE = "en"; + String ERROR_MESSAGE = "This is an error message"; + + interface IMyValidationProvider extends IResourceProvider { + void addException(String theOperation, String theUrl, String theCode, Exception theException); +

void addTerminologyResponse(String theOperation, String theUrl, String theCode, P theReturnParams); + IBaseParameters addTerminologyResponse(String theOperation, String theUrl, String theCode, FhirContext theFhirContext, String theTerminologyResponseFile); + } + + abstract class MyValidationProvider implements IMyValidationProvider { + private final Map myExceptionMap = new HashMap<>(); + private boolean myShouldThrowExceptionForResourceNotFound = true; + private final Map myTerminologyResponseMap = new HashMap<>(); + private final Map myTerminologyResourceMap = new HashMap<>(); + + static String getInputKey(String theOperation, String theUrl, String theCode) { + return theOperation + "-" + theUrl + "#" + theCode; + } + + public void setShouldThrowExceptionForResourceNotFound(boolean theShouldThrowExceptionForResourceNotFound) { + myShouldThrowExceptionForResourceNotFound = theShouldThrowExceptionForResourceNotFound; + } + + public void addException(String theOperation, String theUrl, String theCode, Exception theException) { + String inputKey = getInputKey(theOperation, theUrl, theCode); + myExceptionMap.put(inputKey, theException); + } + + abstract Class getParameterType(); + + @Override + public

void addTerminologyResponse(String theOperation, String theUrl, String theCode, P theReturnParams) { + myTerminologyResponseMap.put(getInputKey(theOperation, theUrl, theCode), theReturnParams); + } + + public IBaseParameters addTerminologyResponse(String theOperation, String theUrl, String theCode, FhirContext theFhirContext, String theTerminologyResponseFile) { + IBaseParameters responseParams = ClasspathUtil.loadResource(theFhirContext, getParameterType(), theTerminologyResponseFile); + addTerminologyResponse(theOperation, theUrl, theCode, responseParams); + return responseParams; + } + + protected void addTerminologyResource(String theUrl, T theResource) { + myTerminologyResourceMap.put(theUrl, theResource); + } + + public abstract T addTerminologyResource(String theUrl); + + protected IBaseParameters getTerminologyResponse(String theOperation, String theUrl, String theCode) throws Exception { + String inputKey = getInputKey(theOperation, theUrl, theCode); + if (myExceptionMap.containsKey(inputKey)) { + throw myExceptionMap.get(inputKey); + } + IBaseParameters params = myTerminologyResponseMap.get(inputKey); + if (params == null) { + throw new IllegalStateException("Test setup incomplete. Missing return params for " + inputKey); + } + return params; + } + + protected T getTerminologyResource(UriParam theUrlParam) { + if (theUrlParam.isEmpty()) { + throw new IllegalStateException("CodeSystem url should not be null."); + } + String urlValue = theUrlParam.getValue(); + if (!myTerminologyResourceMap.containsKey(urlValue) && myShouldThrowExceptionForResourceNotFound) { + throw new IllegalStateException("Test setup incomplete. CodeSystem not found " + urlValue); + } + return myTerminologyResourceMap.get(urlValue); + } + + @Search + public List find(@RequiredParam(name = "url") UriParam theUrlParam) { + T resource = getTerminologyResource(theUrlParam); + return resource != null ? List.of(resource) : List.of(); + } + } + + interface IMyLookupCodeProvider extends IResourceProvider { + void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult); + } +} diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java new file mode 100644 index 000000000000..95c01392212c --- /dev/null +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java @@ -0,0 +1,118 @@ +package ca.uhn.fhir.test.utilities.validation; + +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import jakarta.servlet.http.HttpServletRequest; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.List; + +public interface IValidationProvidersDstu3 { + @SuppressWarnings("unused") + class MyCodeSystemProviderDstu3 extends IValidationProviders.MyValidationProvider { + @Operation(name = "$validate-code", idempotent = true, returnParameters = { + @OperationParam(name = "result", type = BooleanType.class, min = 1), + @OperationParam(name = "message", type = StringType.class), + @OperationParam(name = "display", type = StringType.class) + }) + public IBaseParameters validateCode( + HttpServletRequest theServletRequest, + @IdParam(optional = true) IdType theId, + @OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl, + @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, + @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay + ) throws Exception { + String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$validate-code", url, code); + } + + @Operation(name = "$lookup", idempotent = true, returnParameters= { + @OperationParam(name = "name", type = StringType.class, min = 1), + @OperationParam(name = "version", type = StringType.class), + @OperationParam(name = "display", type = StringType.class, min = 1), + @OperationParam(name = "abstract", type = BooleanType.class, min = 1), + @OperationParam(name = "property", type = StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED) + }) + public IBaseParameters lookup( + HttpServletRequest theServletRequest, + @OperationParam(name = "code", max = 1) CodeType theCode, + @OperationParam(name = "system",max = 1) UriType theSystem, + @OperationParam(name = "coding", max = 1) Coding theCoding, + @OperationParam(name = "version", max = 1) StringType theVersion, + @OperationParam(name = "displayLanguage", max = 1) CodeType theDisplayLanguage, + @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, + RequestDetails theRequestDetails + ) throws Exception { + String url = theSystem != null ? theSystem.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$lookup", url, code); + } + @Override + public Class getResourceType() { + return CodeSystem.class; + } + @Override + Class getParameterType() { + return Parameters.class; + } + @Override + public CodeSystem addTerminologyResource(String theUrl) { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setId(theUrl.substring(0, theUrl.lastIndexOf("/"))); + codeSystem.setUrl(theUrl); + addTerminologyResource(theUrl, codeSystem); + return codeSystem; + } + } + + @SuppressWarnings("unused") + class MyValueSetProviderDstu3 extends IValidationProviders.MyValidationProvider { + @Operation(name = "$validate-code", idempotent = true, returnParameters = { + @OperationParam(name = "result", type = BooleanType.class, min = 1), + @OperationParam(name = "message", type = StringType.class), + @OperationParam(name = "display", type = StringType.class) + }) + public IBaseParameters validateCode( + HttpServletRequest theServletRequest, + @IdParam(optional = true) IdType theId, + @OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl, + @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, + @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, + @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, + @OperationParam(name = "valueSet") ValueSet theValueSet + ) throws Exception { + String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$validate-code", url, code); + } + @Override + public Class getResourceType() { + return ValueSet.class; + } + @Override + Class getParameterType() { + return Parameters.class; + } + @Override + public ValueSet addTerminologyResource(String theUrl) { + ValueSet valueSet = new ValueSet(); + valueSet.setId(theUrl.substring(0, theUrl.lastIndexOf("/"))); + valueSet.setUrl(theUrl); + addTerminologyResource(theUrl, valueSet); + return valueSet; + } + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java similarity index 55% rename from hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java rename to hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java index fd03a8163ffa..504fd90d7002 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java @@ -1,12 +1,10 @@ -package org.hl7.fhir.r4.validation; +package ca.uhn.fhir.test.utilities.validation; -import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import jakarta.servlet.http.HttpServletRequest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.BooleanType; @@ -21,38 +19,29 @@ import java.util.List; -public interface IValidateCodeProvidersR4 { +public interface IValidationProvidersR4 { @SuppressWarnings("unused") - class MyCodeSystemProviderR4 implements IValidationProviders.IMyCodeSystemProvider { - private UriType mySystemUrl; - private CodeType myCode; - private StringType myDisplay; - private Exception myException; - private Parameters myReturnParams; + class MyCodeSystemProviderR4 extends IValidationProviders.MyValidationProvider { - @Operation(name = "validate-code", idempotent = true, returnParameters = { + @Operation(name = "$validate-code", idempotent = true, returnParameters = { @OperationParam(name = "result", type = BooleanType.class, min = 1), @OperationParam(name = "message", type = StringType.class), @OperationParam(name = "display", type = StringType.class) }) - public Parameters validateCode( + public IBaseParameters validateCode( HttpServletRequest theServletRequest, @IdParam(optional = true) IdType theId, @OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl, @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay ) throws Exception { - mySystemUrl = theCodeSystemUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; + String url = theCodeSystemUrl != null ? theCodeSystemUrl.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$validate-code", url, code); } - @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { + @Operation(name = "$lookup", idempotent = true, returnParameters= { @OperationParam(name = "name", type = StringType.class, min = 1), @OperationParam(name = "version", type = StringType.class), @OperationParam(name = "display", type = StringType.class, min = 1), @@ -69,54 +58,39 @@ public IBaseParameters lookup( @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) throws Exception { - mySystemUrl = theSystem; - myCode = theCode; - if (myException != null) { - throw myException; - } - return myReturnParams; + String url = theSystem != null ? theSystem.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$lookup", url, code); } - @Override public Class getResourceType() { return CodeSystem.class; } - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; + Class getParameterType() { + return Parameters.class; } + @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - public String getDisplay() { - return myDisplay != null ? myDisplay.getValue() : null; + public CodeSystem addTerminologyResource(String theUrl) { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setId(theUrl.substring(0, theUrl.lastIndexOf("/"))); + codeSystem.setUrl(theUrl); + addTerminologyResource(theUrl, codeSystem); + return codeSystem; } } @SuppressWarnings("unused") - class MyValueSetProviderR4 implements IValidationProviders.IMyValueSetProvider { - private Exception myException; - private Parameters myReturnParams; - private UriType mySystemUrl; - private UriType myValueSetUrl; - private CodeType myCode; - private StringType myDisplay; + class MyValueSetProviderR4 extends IValidationProviders.MyValidationProvider { - @Operation(name = "validate-code", idempotent = true, returnParameters = { + @Operation(name = "$validate-code", idempotent = true, returnParameters = { @OperationParam(name = "result", type = BooleanType.class, min = 1), @OperationParam(name = "message", type = StringType.class), @OperationParam(name = "display", type = StringType.class) }) - public Parameters validateCode( + public IBaseParameters validateCode( HttpServletRequest theServletRequest, @IdParam(optional = true) IdType theId, @OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl, @@ -125,41 +99,25 @@ public Parameters validateCode( @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, @OperationParam(name = "valueSet") ValueSet theValueSet ) throws Exception { - mySystemUrl = theSystem; - myValueSetUrl = theValueSetUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; + String url = theValueSetUrl != null ? theValueSetUrl.getValue() : null; + String code = theCode != null ? theCode.getValue() : null; + return getTerminologyResponse("$validate-code", url, code); } - @Override public Class getResourceType() { return ValueSet.class; } - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; + Class getParameterType() { + return Parameters.class; } @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - @Override - public String getValueSet() { - return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null; - } - public String getDisplay() { - return myDisplay != null ? myDisplay.getValue() : null; + public ValueSet addTerminologyResource(String theUrl) { + ValueSet valueSet = new ValueSet(); + valueSet.setId(theUrl.substring(0, theUrl.lastIndexOf("/"))); + valueSet.setUrl(theUrl); + addTerminologyResource(theUrl, valueSet); + return valueSet; } } } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java index 1b12e20ec62e..7a05e197ae63 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -190,7 +190,7 @@ protected CodeValidationResult getValidateCodeResultError(final String theMessag return new CodeValidationResult() .setSeverity(IssueSeverity.ERROR) .setMessage(theMessage) - .setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue( + .setIssues(Collections.singletonList(new CodeValidationIssue( theMessage, IssueSeverity.ERROR, CodeValidationIssueCode.INVALID, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index 67e553e3d3b7..a617e04c41b7 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -28,7 +28,6 @@ import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.Enumerations; -import org.hl7.fhir.utilities.validation.ValidationMessage; import java.util.ArrayList; import java.util.Collections; @@ -258,7 +257,7 @@ public CodeValidationResult validateCodeInValueSet( theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); } catch (ExpansionCouldNotBeCompletedInternallyException e) { CodeValidationResult codeValidationResult = new CodeValidationResult(); - codeValidationResult.setSeverityCode("error"); + codeValidationResult.setSeverity(IssueSeverity.ERROR); String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + theCodeSystemUrlAndVersion + "#" + theCode; @@ -267,7 +266,7 @@ public CodeValidationResult validateCodeInValueSet( } codeValidationResult.setMessage(msg); - codeValidationResult.addCodeValidationIssue(e.getCodeValidationIssue()); + codeValidationResult.addIssue(e.getCodeValidationIssue()); return codeValidationResult; } @@ -551,18 +550,18 @@ private CodeValidationResult validateCodeInExpandedValueSet( if (valueSetResult != null) { codeValidationResult = valueSetResult; } else { - ValidationMessage.IssueSeverity severity; + IValidationSupport.IssueSeverity severity; String message; CodeValidationIssueCode issueCode = CodeValidationIssueCode.CODE_INVALID; CodeValidationIssueCoding issueCoding = CodeValidationIssueCoding.INVALID_CODE; if ("fragment".equals(codeSystemResourceContentMode)) { - severity = ValidationMessage.IssueSeverity.WARNING; + severity = IValidationSupport.IssueSeverity.WARNING; message = "Unknown code in fragment CodeSystem '" + getFormattedCodeSystemAndCodeForMessage( theCodeSystemUrlAndVersionToValidate, theCodeToValidate) + "'"; } else { - severity = ValidationMessage.IssueSeverity.ERROR; + severity = IValidationSupport.IssueSeverity.ERROR; message = "Unknown code '" + getFormattedCodeSystemAndCodeForMessage( theCodeSystemUrlAndVersionToValidate, theCodeToValidate) @@ -574,10 +573,9 @@ private CodeValidationResult validateCodeInExpandedValueSet( } codeValidationResult = new CodeValidationResult() - .setSeverityCode(severity.toCode()) + .setSeverity(severity) .setMessage(message) - .addCodeValidationIssue(new CodeValidationIssue( - message, getIssueSeverityFromCodeValidationIssue(severity), issueCode, issueCoding)); + .addIssue(new CodeValidationIssue(message, severity, issueCode, issueCoding)); } return codeValidationResult; @@ -589,19 +587,6 @@ private static String getFormattedCodeSystemAndCodeForMessage( + theCodeToValidate; } - private IValidationSupport.IssueSeverity getIssueSeverityFromCodeValidationIssue( - ValidationMessage.IssueSeverity theSeverity) { - switch (theSeverity) { - case ERROR: - return IValidationSupport.IssueSeverity.ERROR; - case WARNING: - return IValidationSupport.IssueSeverity.WARNING; - case INFORMATION: - return IValidationSupport.IssueSeverity.INFORMATION; - } - return null; - } - private CodeValidationResult findCodeInExpansion( String theCodeToValidate, String theDisplayToValidate, @@ -1123,8 +1108,8 @@ private boolean expandValueSetR5IncludeOrExclude( new CodeValidationIssue( theMessage, IssueSeverity.ERROR, - CodeValidationIssueCode.OTHER, - CodeValidationIssueCoding.OTHER)); + CodeValidationIssueCode.INVALID, + CodeValidationIssueCoding.VS_INVALID)); } for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { @@ -1376,7 +1361,7 @@ private static CodeValidationResult createResultForDisplayMismatch( .setCodeSystemVersion(theCodeSystemVersion) .setDisplay(theExpectedDisplay); if (issueSeverity != null) { - codeValidationResult.setCodeValidationIssues(Collections.singletonList(new CodeValidationIssue( + codeValidationResult.setIssues(Collections.singletonList(new CodeValidationIssue( message, theIssueSeverityForCodeDisplayMismatch, CodeValidationIssueCode.INVALID, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java index 370f8b423dd2..398eacc52a24 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java @@ -28,6 +28,7 @@ import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Parameters; @@ -631,7 +632,7 @@ private CodeValidationResult createErrorCodeValidationResult( return new CodeValidationResult() .setSeverity(severity) .setMessage(theMessage) - .addCodeValidationIssue(new CodeValidationIssue( + .addIssue(new CodeValidationIssue( theMessage, severity, theIssueCode, CodeValidationIssueCoding.INVALID_CODE)); } @@ -680,13 +681,13 @@ private CodeValidationResult createCodeValidationResult( createCodeValidationIssues( (IBaseOperationOutcome) issuesValue.get(), fhirContext.getVersion().getVersion()) - .ifPresent(i -> i.forEach(result::addCodeValidationIssue)); + .ifPresent(i -> i.forEach(result::addIssue)); } else { // create a validation issue out of the message // this is a workaround to overcome an issue in the FHIR Validator library // where ValueSet bindings are only reading issues but not messages // @see https://github.com/hapifhir/org.hl7.fhir.core/issues/1766 - result.addCodeValidationIssue(createCodeValidationIssue(result.getMessage())); + result.addIssue(createCodeValidationIssue(result.getMessage())); } return result; } @@ -717,23 +718,42 @@ public static Optional> createCodeValidationIssu private static Collection createCodeValidationIssuesR4(OperationOutcome theOperationOutcome) { return theOperationOutcome.getIssue().stream() - .map(issueComponent -> - createCodeValidationIssue(issueComponent.getDetails().getText())) + .map(issueComponent -> { + String diagnostics = issueComponent.getDiagnostics(); + IssueSeverity issueSeverity = + IssueSeverity.fromCode(issueComponent.getSeverity().toCode()); + String issueTypeCode = issueComponent.getCode().toCode(); + CodeableConcept details = issueComponent.getDetails(); + CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode); + CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText()); + details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode())); + issue.setDetails(issueDetails); + return issue; + }) .collect(Collectors.toList()); } private static Collection createCodeValidationIssuesDstu3( org.hl7.fhir.dstu3.model.OperationOutcome theOperationOutcome) { return theOperationOutcome.getIssue().stream() - .map(issueComponent -> - createCodeValidationIssue(issueComponent.getDetails().getText())) + .map(issueComponent -> { + String diagnostics = issueComponent.getDiagnostics(); + IssueSeverity issueSeverity = + IssueSeverity.fromCode(issueComponent.getSeverity().toCode()); + String issueTypeCode = issueComponent.getCode().toCode(); + org.hl7.fhir.dstu3.model.CodeableConcept details = issueComponent.getDetails(); + CodeValidationIssue issue = new CodeValidationIssue(diagnostics, issueSeverity, issueTypeCode); + CodeValidationIssueDetails issueDetails = new CodeValidationIssueDetails(details.getText()); + details.getCoding().forEach(coding -> issueDetails.addCoding(coding.getSystem(), coding.getCode())); + issue.setDetails(issueDetails); + return issue; + }) .collect(Collectors.toList()); } private static CodeValidationIssue createCodeValidationIssue(String theMessage) { return new CodeValidationIssue( theMessage, - // assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match IssueSeverity.ERROR, CodeValidationIssueCode.INVALID, CodeValidationIssueCoding.INVALID_CODE); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java index 1898292c4512..265debed058b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java @@ -87,7 +87,7 @@ public CodeValidationResult validateCode( result.setSeverity(null); result.setMessage(null); } else { - result.addCodeValidationIssue(new CodeValidationIssue( + result.addIssue(new CodeValidationIssue( theMessage, myNonExistentCodeSystemSeverity, CodeValidationIssueCode.NOT_FOUND, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java index 7321f33d8c8e..7093ab2a7ff1 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportUtils.java @@ -11,6 +11,17 @@ public final class ValidationSupportUtils { private ValidationSupportUtils() {} + /** + * This method extracts a code system that can be (potentially) associated with a code when + * performing validation against a ValueSet. This method was created for internal purposes. + * Please use this method with care because it will only cover some + * use-cases (e.g. standard bindings) while for others it may not return correct results or return null. + * An incorrect result could be considered if the resource declares a code with a system, and you're calling + * this method to check a binding against a ValueSet that has nothing to do with that system. + * @param theValueSet the valueSet + * @param theCode the code + * @return the system which can be associated with the code + */ public static String extractCodeSystemForCode(IBaseResource theValueSet, String theCode) { if (theValueSet instanceof org.hl7.fhir.dstu3.model.ValueSet) { return extractCodeSystemForCodeDSTU3((org.hl7.fhir.dstu3.model.ValueSet) theValueSet, theCode); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index f0f3f41e7f9b..393d8ce1dc54 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -62,6 +62,7 @@ import java.util.Objects; import java.util.Set; +import static ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -296,7 +297,7 @@ private ValidationResult convertValidationResult( theResult.getCodeSystemVersion(), conceptDefinitionComponent, display, - getIssuesForCodeValidation(theResult.getCodeValidationIssues())); + getIssuesForCodeValidation(theResult.getIssues())); } if (retVal == null) { @@ -307,73 +308,36 @@ private ValidationResult convertValidationResult( } private List getIssuesForCodeValidation( - List codeValidationIssues) { - List issues = new ArrayList<>(); - - for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeValidationIssues) { - - CodeableConcept codeableConcept = new CodeableConcept().setText(codeValidationIssue.getMessage()); - codeableConcept.addCoding( - "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", - getIssueCodingFromCodeValidationIssue(codeValidationIssue), - null); - - OperationOutcome.OperationOutcomeIssueComponent issue = + List theIssues) { + List issueComponents = new ArrayList<>(); + + for (IValidationSupport.CodeValidationIssue issue : theIssues) { + OperationOutcome.IssueSeverity severity = + OperationOutcome.IssueSeverity.fromCode(issue.getSeverity().getCode()); + OperationOutcome.IssueType issueType = + OperationOutcome.IssueType.fromCode(issue.getType().getCode()); + String diagnostics = issue.getDiagnostics(); + + IValidationSupport.CodeValidationIssueDetails details = issue.getDetails(); + CodeableConcept codeableConcept = new CodeableConcept().setText(details.getText()); + details.getCodings().forEach(detailCoding -> codeableConcept + .addCoding() + .setSystem(detailCoding.getSystem()) + .setCode(detailCoding.getCode())); + + OperationOutcome.OperationOutcomeIssueComponent issueComponent = new OperationOutcome.OperationOutcomeIssueComponent() - .setSeverity(getIssueSeverityFromCodeValidationIssue(codeValidationIssue)) - .setCode(getIssueTypeFromCodeValidationIssue(codeValidationIssue)) - .setDetails(codeableConcept); - issue.getDetails().setText(codeValidationIssue.getMessage()); - issue.addExtension() + .setSeverity(severity) + .setCode(issueType) + .setDetails(codeableConcept) + .setDiagnostics(diagnostics); + issueComponent + .addExtension() .setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id") .setValue(new StringType("Terminology_PassThrough_TX_Message")); - issues.add(issue); - } - return issues; - } - - private String getIssueCodingFromCodeValidationIssue(IValidationSupport.CodeValidationIssue codeValidationIssue) { - switch (codeValidationIssue.getCoding()) { - case VS_INVALID: - return "vs-invalid"; - case NOT_FOUND: - return "not-found"; - case NOT_IN_VS: - return "not-in-vs"; - case INVALID_CODE: - return "invalid-code"; - case INVALID_DISPLAY: - return "invalid-display"; - } - return null; - } - - private OperationOutcome.IssueType getIssueTypeFromCodeValidationIssue( - IValidationSupport.CodeValidationIssue codeValidationIssue) { - switch (codeValidationIssue.getCode()) { - case NOT_FOUND: - return OperationOutcome.IssueType.NOTFOUND; - case CODE_INVALID: - return OperationOutcome.IssueType.CODEINVALID; - case INVALID: - return OperationOutcome.IssueType.INVALID; - } - return null; - } - - private OperationOutcome.IssueSeverity getIssueSeverityFromCodeValidationIssue( - IValidationSupport.CodeValidationIssue codeValidationIssue) { - switch (codeValidationIssue.getSeverity()) { - case FATAL: - return OperationOutcome.IssueSeverity.FATAL; - case ERROR: - return OperationOutcome.IssueSeverity.ERROR; - case WARNING: - return OperationOutcome.IssueSeverity.WARNING; - case INFORMATION: - return OperationOutcome.IssueSeverity.INFORMATION; + issueComponents.add(issueComponent); } - return null; + return issueComponents; } @Override @@ -851,25 +815,22 @@ private IValidationSupport.CodeValidationResult validateCodeInValueSet( .getRootValidationSupport() .validateCodeInValueSet( myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet); - if (result != null) { + if (result != null && theSystem != null) { /* We got a value set result, which could be successful, or could contain errors/warnings. The code might also be invalid in the code system, so we will check that as well and add those issues to our result. */ IValidationSupport.CodeValidationResult codeSystemResult = validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay); - final boolean valueSetResultContainsInvalidDisplay = result.getCodeValidationIssues().stream() - .anyMatch(codeValidationIssue -> codeValidationIssue.getCoding() - == IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY); + final boolean valueSetResultContainsInvalidDisplay = result.getIssues().stream() + .anyMatch(VersionSpecificWorkerContextWrapper::hasInvalidDisplayDetailCode); if (codeSystemResult != null) { - for (IValidationSupport.CodeValidationIssue codeValidationIssue : - codeSystemResult.getCodeValidationIssues()) { + for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeSystemResult.getIssues()) { /* Value set validation should already have checked the display name. If we get INVALID_DISPLAY issues from code system validation, they will only repeat what was already caught. */ - if (codeValidationIssue.getCoding() != IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY - || !valueSetResultContainsInvalidDisplay) { - result.addCodeValidationIssue(codeValidationIssue); + if (!hasInvalidDisplayDetailCode(codeValidationIssue) || !valueSetResultContainsInvalidDisplay) { + result.addIssue(codeValidationIssue); } } } @@ -877,6 +838,10 @@ private IValidationSupport.CodeValidationResult validateCodeInValueSet( return result; } + private static boolean hasInvalidDisplayDetailCode(IValidationSupport.CodeValidationIssue theIssue) { + return theIssue.hasIssueDetailCode(INVALID_DISPLAY.getCode()); + } + private IValidationSupport.CodeValidationResult validateCodeInCodeSystem( ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) { return myValidationSupportContext diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java index bb7eaf1c17ba..eac448ad0ebd 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ILookupCodeTest.java @@ -9,6 +9,7 @@ import ca.uhn.fhir.context.support.IValidationSupport.StringConceptProperty; import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.junit.jupiter.api.Test; @@ -21,12 +22,12 @@ import static ca.uhn.fhir.context.support.IValidationSupport.TYPE_STRING; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_NAME; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.LANGUAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_NAME; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.LANGUAGE; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createConceptProperty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -189,8 +190,6 @@ default void verifyLookupCodeResult(LookupCodeRequest theRequest, LookupCodeResu // verify assertNotNull(outcome); - assertEquals(theRequest.getCode(), getLookupCodeProvider().getCode()); - assertEquals(theRequest.getSystem(), getLookupCodeProvider().getSystem()); assertEquals(theExpectedResult.isFound(), outcome.isFound()); assertEquals(theExpectedResult.getErrorMessage(), outcome.getErrorMessage()); assertEquals(theExpectedResult.getCodeSystemDisplayName(), outcome.getCodeSystemDisplayName()); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java index 5ba79bd3e6f9..95c4fd7d3b97 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyLookupCodeTest.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult; import ca.uhn.fhir.context.support.LookupCodeRequest; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.junit.jupiter.api.Test; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java index cb6bb02ac072..21c5d0dc5e74 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IRemoteTerminologyValidateCodeTest.java @@ -1,17 +1,76 @@ package org.hl7.fhir.common.hapi.validation; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.IValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.junit.jupiter.api.Test; import java.util.Collection; import java.util.List; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.createCodeValidationIssues; + public interface IRemoteTerminologyValidateCodeTest extends IValidateCodeTest { default List getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) { // this method should be removed once support for issues is fully implemented across all validator types Optional> issues = RemoteTerminologyServiceValidationSupport.createCodeValidationIssues(theOperationOutcome, getService().getFhirContext().getVersion().getVersion()); return issues.map(theCodeValidationIssues -> theCodeValidationIssues.stream().toList()).orElseGet(List::of); } + + @Test + default void createCodeValidationIssues_withCodeSystemOutcomeForInvalidCode_returnsAsExpected() { + IBaseOperationOutcome outcome = getCodeSystemInvalidCodeOutcome(); + FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion(); + Optional> issuesOptional = createCodeValidationIssues(outcome, versionEnum); + assertThat(issuesOptional).isPresent(); + assertThat(issuesOptional.get()).hasSize(1); + IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next(); + assertThat(issue.getType().getCode()).isEqualTo("code-invalid"); + assertThat(issue.getSeverity().getCode()).isEqualTo("error"); + assertThat(issue.getDetails().getCodings()).hasSize(1); + IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0); + assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type"); + assertThat(issueCoding.getCode()).isEqualTo("invalid-code"); + assertThat(issue.getDetails().getText()).isEqualTo("Unknown code 'CODE' in the CodeSystem 'http://code.system/url' version '1.0.0'"); + assertThat(issue.getDiagnostics()).isNull(); + } + + @Test + default void createCodeValidationIssues_withValueSetOutcomeForInvalidCode_returnsAsExpected() { + IBaseOperationOutcome outcome = getValueSetInvalidCodeOutcome(); + FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion(); + Optional> issuesOptional = createCodeValidationIssues(outcome, versionEnum); + assertThat(issuesOptional).isPresent(); + assertThat(issuesOptional.get()).hasSize(2); + IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next(); + assertThat(issue.getType().getCode()).isEqualTo("code-invalid"); + assertThat(issue.getSeverity().getCode()).isEqualTo("error"); + assertThat(issue.getDetails().getCodings()).hasSize(1); + IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0); + assertThat(issueCoding.getSystem()).isEqualTo("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type"); + assertThat(issueCoding.getCode()).isEqualTo("not-in-vs"); + assertThat(issue.getDetails().getText()).isEqualTo("The provided code 'http://code.system/url#CODE' was not found in the value set 'http://value.set/url%7C1.0.0'"); + assertThat(issue.getDiagnostics()).isNull(); + } + + @Test + default void createCodeValidationIssues_withValueSetOutcomeWithCustomDetailCode_returnsAsExpected() { + IBaseOperationOutcome outcome = getValueSetCustomDetailCodeOutcome(); + FhirVersionEnum versionEnum = getService().getFhirContext().getVersion().getVersion(); + Optional> issuesOptional = createCodeValidationIssues(outcome, versionEnum); + assertThat(issuesOptional).isPresent(); + assertThat(issuesOptional.get()).hasSize(1); + IValidationSupport.CodeValidationIssue issue = issuesOptional.get().iterator().next(); + assertThat(issue.getType().getCode()).isEqualTo("processing"); + assertThat(issue.getSeverity().getCode()).isEqualTo("information"); + assertThat(issue.getDetails().getCodings()).hasSize(1); + IValidationSupport.CodeValidationIssueCoding issueCoding = issue.getDetails().getCodings().get(0); + assertThat(issueCoding.getSystem()).isEqualTo("http://example.com/custom-issue-type"); + assertThat(issueCoding.getCode()).isEqualTo("valueset-is-draft"); + assertThat(issue.getDetails().getText()).isNull(); + assertThat(issue.getDiagnostics()).isEqualTo("The ValueSet status is marked as draft."); + } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java index 52dbf1177a86..53411b440fe6 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidateCodeTest.java @@ -4,6 +4,9 @@ import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.util.ClasspathUtil; +import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -16,12 +19,13 @@ import java.util.stream.Stream; import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.CODE_SYSTEM_VERSION; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.DISPLAY; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.ERROR_MESSAGE; -import static org.hl7.fhir.common.hapi.validation.IValidationProviders.VALUE_SET_URL; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_VALIDATE_CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -31,21 +35,35 @@ public interface IValidateCodeTest { - IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider(); - IValidationProviders.IMyValueSetProvider getValueSetProvider(); + IValidationProviders.IMyValidationProvider getCodeSystemProvider(); + IValidationProviders.IMyValidationProvider getValueSetProvider(); IValidationSupport getService(); IBaseParameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource); String getCodeSystemError(); String getValueSetError(); IBaseOperationOutcome getCodeSystemInvalidCodeOutcome(); IBaseOperationOutcome getValueSetInvalidCodeOutcome(); + IBaseOperationOutcome getValueSetCustomDetailCodeOutcome(); + + default IBaseOperationOutcome getCodeSystemInvalidCodeOutcome(Class theResourceClass) { + return getOutcome(theResourceClass, "/terminology/OperationOutcome-CodeSystem-invalid-code.json"); + } + default IBaseOperationOutcome getValueSetInvalidCodeOutcome(Class theResourceClass) { + return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-invalid-code.json"); + } + default IBaseOperationOutcome getValueSetCustomDetailCodeOutcome(Class theResourceClass) { + return getOutcome(theResourceClass, "/terminology/OperationOutcome-ValueSet-custom-issue-detail.json"); + } + default IBaseOperationOutcome getOutcome(Class theResourceClass, String theFile) { + return ClasspathUtil.loadResource(getService().getFhirContext(), theResourceClass, theFile); + } default void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - getCodeSystemProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); + getCodeSystemProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, CODE_SYSTEM, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource)); } default void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - getValueSetProvider().setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); + getValueSetProvider().addTerminologyResponse(OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, createParameters(theResult, theDisplay, theMessage, theIssuesResource)); } @Test @@ -91,8 +109,8 @@ default void validateCode_codeSystemAndValueSetUrlAreIncorrect_returnsValidation String theValidationMessage, String theCodeSystem, String theValueSetUrl) { - getCodeSystemProvider().setException(theException); - getValueSetProvider().setException(theException); + getCodeSystemProvider().addException(OPERATION_VALIDATE_CODE, theCodeSystem, CODE, theException); + getValueSetProvider().addException(OPERATION_VALIDATE_CODE, theValueSetUrl, CODE, theException); CodeValidationResult outcome = getService().validateCode(null, null, theCodeSystem, CODE, DISPLAY, theValueSetUrl); verifyErrorResultFromException(outcome, theValidationMessage, theServerMessage); @@ -105,7 +123,7 @@ default void verifyErrorResultFromException(CodeValidationResult outcome, String for (String message : theMessages) { assertTrue(outcome.getMessage().contains(message)); } - assertFalse(outcome.getCodeValidationIssues().isEmpty()); + assertFalse(outcome.getIssues().isEmpty()); } @Test @@ -130,11 +148,7 @@ default void validateCode_withValueSetSuccess_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -147,9 +161,7 @@ default void validateCode_withCodeSystemSuccess_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getCodeSystemProvider().getCode()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -165,10 +177,7 @@ default void validateCode_withCodeSystemProvidingMinimalInputs_ReturnsSuccess() assertNull(outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getCodeSystemProvider().getCode()); - assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -184,15 +193,11 @@ default void validateCode_withCodeSystemSuccessWithMessageValue_returnsCorrectly assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getCodeSystemProvider().getCode()); - assertEquals(DISPLAY, getCodeSystemProvider().getDisplay()); - assertEquals(CODE_SYSTEM, getCodeSystemProvider().getSystem()); + assertTrue(outcome.getIssues().isEmpty()); } @Test - default void validateCode_withCodeSystemError_returnsCorrectly() { + default void validateCode_withCodeSystemErrorWithDiagnosticsWithIssues_returnsCorrectly() { IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome(); createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, invalidCodeOutcome); @@ -204,12 +209,12 @@ default void validateCode_withCodeSystemError_returnsCorrectly() { // assertEquals(CODE, outcome.getCode()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(getCodeSystemError(), outcome.getMessage()); - assertFalse(outcome.getCodeValidationIssues().isEmpty()); + assertFalse(outcome.getIssues().isEmpty()); verifyIssues(invalidCodeOutcome, outcome); } @Test - default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() { + default void validateCode_withCodeSystemErrorWithDiagnosticsWithoutIssues_returnsCorrectly() { createCodeSystemReturnParameters(false, null, ERROR_MESSAGE, null); CodeValidationResult outcome = getService() @@ -223,10 +228,32 @@ default void validateCode_withCodeSystemErrorAndIssues_returnsCorrectly() { assertNull(outcome.getDisplay()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(expectedError, outcome.getMessage()); - assertFalse(outcome.getCodeValidationIssues().isEmpty()); - assertEquals(1, outcome.getCodeValidationIssues().size()); - assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage()); - assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity()); + assertFalse(outcome.getIssues().isEmpty()); + assertEquals(1, outcome.getIssues().size()); + assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics()); + assertEquals(ERROR, outcome.getIssues().get(0).getSeverity()); + } + + @Test + default void validateCode_withCodeSystemErrorWithoutDiagnosticsWithIssues_returnsCorrectly() { + IBaseOperationOutcome invalidCodeOutcome = getCodeSystemInvalidCodeOutcome(); + createCodeSystemReturnParameters(false, null, null, invalidCodeOutcome); + + CodeValidationResult outcome = getService() + .validateCode(null, null, CODE_SYSTEM, CODE, null, null); + + String expectedError = getCodeSystemError(); + assertNotNull(outcome); + assertEquals(CODE_SYSTEM, outcome.getCodeSystemName()); + assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion()); + // assertEquals(CODE, outcome.getCode()); + assertNull(outcome.getDisplay()); + assertEquals(ERROR, outcome.getSeverity()); + assertNull(outcome.getMessage()); + assertFalse(outcome.getIssues().isEmpty()); + assertEquals(1, outcome.getIssues().size()); + assertNull(outcome.getIssues().get(0).getDiagnostics()); + assertEquals(ERROR, outcome.getIssues().get(0).getSeverity()); } @Test @@ -242,10 +269,7 @@ default void validateCode_withValueSetProvidingMinimalInputsSuccess_returnsCorre assertNull(outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -261,11 +285,7 @@ default void validateCode_withValueSetSuccessWithMessage_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - assertTrue(outcome.getCodeValidationIssues().isEmpty()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertTrue(outcome.getIssues().isEmpty()); } @Test @@ -283,13 +303,9 @@ default void validateCode_withValueSetError_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(expectedError, outcome.getMessage()); - assertEquals(1, outcome.getCodeValidationIssues().size()); - assertEquals(expectedError, outcome.getCodeValidationIssues().get(0).getMessage()); - assertEquals(ERROR, outcome.getCodeValidationIssues().get(0).getSeverity()); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); + assertEquals(1, outcome.getIssues().size()); + assertEquals(expectedError, outcome.getIssues().get(0).getDiagnostics()); + assertEquals(ERROR, outcome.getIssues().get(0).getSeverity()); } @Test @@ -306,24 +322,28 @@ default void validateCode_withValueSetErrorWithIssues_returnsCorrectly() { assertEquals(DISPLAY, outcome.getDisplay()); assertEquals(ERROR, outcome.getSeverity()); assertEquals(getValueSetError(), outcome.getMessage()); - assertFalse(outcome.getCodeValidationIssues().isEmpty()); + assertFalse(outcome.getIssues().isEmpty()); verifyIssues(invalidCodeOutcome, outcome); - - assertEquals(CODE, getValueSetProvider().getCode()); - assertEquals(DISPLAY, getValueSetProvider().getDisplay()); - assertEquals(VALUE_SET_URL, getValueSetProvider().getValueSet()); } default void verifyIssues(IBaseOperationOutcome theOperationOutcome, CodeValidationResult theResult) { List issues = getCodeValidationIssues(theOperationOutcome); - assertEquals(issues.size(), theResult.getCodeValidationIssues().size()); + assertEquals(issues.size(), theResult.getIssues().size()); for (int i = 0; i < issues.size(); i++) { IValidationSupport.CodeValidationIssue expectedIssue = issues.get(i); - IValidationSupport.CodeValidationIssue actualIssue = theResult.getCodeValidationIssues().get(i); - assertEquals(expectedIssue.getCode(), actualIssue.getCode()); + IValidationSupport.CodeValidationIssue actualIssue = theResult.getIssues().get(i); + assertEquals(expectedIssue.getType().getCode(), actualIssue.getType().getCode()); assertEquals(expectedIssue.getSeverity(), actualIssue.getSeverity()); - assertEquals(expectedIssue.getCoding(), actualIssue.getCoding()); - assertEquals(expectedIssue.getMessage(), actualIssue.getMessage()); + assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText()); + assertEquals(expectedIssue.getDetails().getCodings().size(), actualIssue.getDetails().getCodings().size()); + for (int index = 0; index < expectedIssue.getDetails().getCodings().size(); index++) { + IValidationSupport.CodeValidationIssueCoding expectedCoding = expectedIssue.getDetails().getCodings().get(index); + IValidationSupport.CodeValidationIssueCoding actualCoding = actualIssue.getDetails().getCodings().get(index); + assertEquals(expectedCoding.getSystem(), actualCoding.getSystem()); + assertEquals(expectedCoding.getCode(), actualCoding.getCode()); + } + assertEquals(expectedIssue.getDetails().getText(), actualIssue.getDetails().getText()); + assertEquals(expectedIssue.getDiagnostics(), actualIssue.getDiagnostics()); } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java deleted file mode 100644 index 1537f8e5c00b..000000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.hl7.fhir.common.hapi.validation; - -import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.rest.server.IResourceProvider; -import org.hl7.fhir.instance.model.api.IBaseParameters; - -public interface IValidationProviders { - String CODE_SYSTEM = "http://code.system/url"; - String CODE_SYSTEM_VERSION = "1.0.0"; - String CODE_SYSTEM_NAME = "Test Code System"; - String CODE = "CODE"; - String VALUE_SET_URL = "http://value.set/url"; - String DISPLAY = "Explanation for code TestCode."; - String LANGUAGE = "en"; - String ERROR_MESSAGE = "This is an error message"; - - interface IMyCodeSystemProvider extends IResourceProvider { - String getCode(); - String getSystem(); - String getDisplay(); - void setException(Exception theException); - void setReturnParams(IBaseParameters theParameters); - } - - interface IMyLookupCodeProvider extends IResourceProvider { - String getCode(); - String getSystem(); - void setLookupCodeResult(IValidationSupport.LookupCodeResult theLookupCodeResult); - } - - interface IMyValueSetProvider extends IResourceProvider { - String getCode(); - String getSystem(); - String getDisplay(); - String getValueSet(); - void setException(Exception theException); - void setReturnParams(IBaseParameters theParameters); - } -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java index 57aae8d96f99..cbc79fadc981 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java @@ -7,7 +7,6 @@ import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; - import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; @@ -16,17 +15,18 @@ import org.junit.jupiter.api.Test; import org.mockito.quality.Strictness; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import java.util.List; - public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestWithInlineMocks { final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes(); @@ -80,7 +80,7 @@ public void cacheResource_normally_executesWithoutException() { } @Test - public void validateCode_normally_resolvesCodeSystemFromValueSet() { + public void validateCode_codeInValueSet_resolvesCodeSystemFromValueSet() { // setup IValidationSupport validationSupport = mockValidationSupport(); ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); @@ -90,8 +90,7 @@ public void validateCode_normally_resolvesCodeSystemFromValueSet() { ValueSet valueSet = new ValueSet(); valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); - when(validationSupport.fetchResource(eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(valueSet); - when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(new IValidationSupport.CodeValidationResult()); + when(validationSupport.validateCodeInValueSet(any(), any(), any(), any(), any(), any())).thenReturn(mock(IValidationSupport.CodeValidationResult.class)); // execute wrapper.validateCode(new ValidationOptions(), "code0", valueSet); @@ -101,6 +100,26 @@ public void validateCode_normally_resolvesCodeSystemFromValueSet() { verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()); } + @Test + public void validateCode_codeNotInValueSet_doesNotResolveSystem() { + // setup + IValidationSupport validationSupport = mockValidationSupport(); + ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); + VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached()); + VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer); + + ValueSet valueSet = new ValueSet(); + valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0"); + valueSet.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2"); + + // execute + wrapper.validateCode(new ValidationOptions(), "code1", valueSet); + + // verify + verify(validationSupport, times(1)).validateCodeInValueSet(any(), any(), eq(null), eq("code1"), any(), any()); + verify(validationSupport, never()).validateCode(any(), any(), any(), any(), any(), any()); + } + @Test public void isPrimitive_primitive() { // setup diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java index da73c0be8000..5a53ba0ac3d4 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu2/hapi/validation/FhirInstanceValidatorDstu2Test.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.resource.Parameters; @@ -28,10 +27,7 @@ import org.hl7.fhir.dstu2.model.QuestionnaireResponse; import org.hl7.fhir.dstu2.model.QuestionnaireResponse.QuestionnaireResponseStatus; import org.hl7.fhir.dstu2.model.StringType; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -41,9 +37,7 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -100,7 +94,7 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca if (myValidConcepts.contains(system + "___" + code)) { retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { - return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code"); + return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage("Unknown code"); } else { retVal = null; } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index d04e90d2d7a8..a8c44097e26a 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -58,7 +58,6 @@ import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -229,10 +228,10 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message); } else if (myCodeSystems.containsKey(system)) { CodeSystem cs = myCodeSystems.get(system); Optional found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst(); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java deleted file mode 100644 index 0c639e310ee2..000000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java +++ /dev/null @@ -1,159 +0,0 @@ -package org.hl7.fhir.dstu3.hapi.validation; - -import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import jakarta.servlet.http.HttpServletRequest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; -import org.hl7.fhir.dstu3.model.BooleanType; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeType; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.Parameters; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.UriType; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import java.util.List; - -public interface IValidateCodeProvidersDstu3 { - @SuppressWarnings("unused") - class MyCodeSystemProviderDstu3 implements IValidationProviders.IMyCodeSystemProvider { - private UriType mySystemUrl; - private CodeType myCode; - private StringType myDisplay; - private Exception myException; - private Parameters myReturnParams; - - @Operation(name = "validate-code", idempotent = true, returnParameters = { - @OperationParam(name = "result", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1), - @OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class), - @OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class) - }) - public org.hl7.fhir.dstu3.model.Parameters validateCode( - HttpServletRequest theServletRequest, - @IdParam(optional = true) org.hl7.fhir.dstu3.model.IdType theId, - @OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theCodeSystemUrl, - @OperationParam(name = "code", min = 0, max = 1) org.hl7.fhir.dstu3.model.CodeType theCode, - @OperationParam(name = "display", min = 0, max = 1) org.hl7.fhir.dstu3.model.StringType theDisplay - ) throws Exception { - mySystemUrl = theCodeSystemUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; - } - - @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { - @OperationParam(name = "name", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1), - @OperationParam(name = "version", type = org.hl7.fhir.dstu3.model.StringType.class), - @OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class, min = 1), - @OperationParam(name = "abstract", type = org.hl7.fhir.dstu3.model.BooleanType.class, min = 1), - @OperationParam(name = "property", type = org.hl7.fhir.dstu3.model.StringType.class, min = 0, max = OperationParam.MAX_UNLIMITED) - }) - public IBaseParameters lookup( - HttpServletRequest theServletRequest, - @OperationParam(name = "code", max = 1) org.hl7.fhir.dstu3.model.CodeType theCode, - @OperationParam(name = "system",max = 1) org.hl7.fhir.dstu3.model.UriType theSystem, - @OperationParam(name = "coding", max = 1) Coding theCoding, - @OperationParam(name = "version", max = 1) org.hl7.fhir.dstu3.model.StringType theVersion, - @OperationParam(name = "displayLanguage", max = 1) org.hl7.fhir.dstu3.model.CodeType theDisplayLanguage, - @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, - RequestDetails theRequestDetails - ) { - myCode = theCode; - return myReturnParams; - } - - @Override - public Class getResourceType() { - return CodeSystem.class; - } - - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - public String getDisplay() { - return myDisplay != null ? myDisplay.getValue() : null; - } - } - - @SuppressWarnings("unused") - class MyValueSetProviderDstu3 implements IValidationProviders.IMyValueSetProvider { - private Exception myException; - private Parameters myReturnParams; - private UriType mySystemUrl; - private UriType myValueSetUrl; - private CodeType myCode; - private StringType myDisplay; - - @Operation(name = "validate-code", idempotent = true, returnParameters = { - @OperationParam(name = "result", type = BooleanType.class, min = 1), - @OperationParam(name = "message", type = org.hl7.fhir.dstu3.model.StringType.class), - @OperationParam(name = "display", type = org.hl7.fhir.dstu3.model.StringType.class) - }) - public Parameters validateCode( - HttpServletRequest theServletRequest, - @IdParam(optional = true) IdType theId, - @OperationParam(name = "url", min = 0, max = 1) org.hl7.fhir.dstu3.model.UriType theValueSetUrl, - @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, - @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, - @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay, - @OperationParam(name = "valueSet") org.hl7.fhir.dstu3.model.ValueSet theValueSet - ) throws Exception { - mySystemUrl = theSystem; - myValueSetUrl = theValueSetUrl; - myCode = theCode; - myDisplay = theDisplay; - if (myException != null) { - throw myException; - } - return myReturnParams; - } - @Override - public Class getResourceType() { - return ValueSet.class; - } - public void setException(Exception theException) { - myException = theException; - } - @Override - public void setReturnParams(IBaseParameters theParameters) { - myReturnParams = (Parameters) theParameters; - } - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } - @Override - public String getValueSet() { - return myValueSetUrl != null ? myValueSetUrl.getValueAsString() : null; - } - public String getDisplay() { - return myDisplay != null ? myDisplay.getValue() : null; - } - } -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java index 7804b9df10f2..57591b31e763 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java @@ -41,7 +41,6 @@ import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -56,6 +55,8 @@ import java.util.List; import java.util.stream.Collectors; +import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.ERROR; +import static ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity.WARNING; import static org.assertj.core.api.Assertions.assertThat; import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.BOOLEAN; import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.CHOICE; @@ -224,7 +225,7 @@ public void testCodedAnswer() { when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(ValueSet.class))) .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); when(myValSupport.validateCodeInValueSet(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(ValueSet.class))) - .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code")); + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); @@ -246,7 +247,7 @@ public void testCodedAnswer() { when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) .thenReturn(new IValidationSupport.CodeValidationResult().setCode(CODE_ICC_SCHOOLTYPE_PT)); when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode("warning").setMessage("Unknown code: http://codesystems.com/system / code1")); + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(WARNING).setMessage("Unknown code: http://codesystems.com/system / code1")); QuestionnaireResponse qa; @@ -1034,7 +1035,7 @@ public void testOpenchoiceAnswer() { when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), nullable(String.class))) .thenReturn(new IValidationSupport.CodeValidationResult().setCode("code0")); when(myValSupport.validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code1"), any(), nullable(String.class))) - .thenReturn(new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage("Unknown code")); + .thenReturn(new IValidationSupport.CodeValidationResult().setSeverity(ERROR).setMessage("Unknown code")); CodeSystem codeSystem = new CodeSystem(); codeSystem.setContent(CodeSystemContentMode.COMPLETE); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java index 6e98c4b31a94..56bc892c3d41 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeDstu3Test.java @@ -12,9 +12,9 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; import jakarta.servlet.http.HttpServletRequest; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.CodeSystem; @@ -164,8 +164,6 @@ void lookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IV @SuppressWarnings("unused") static class MyLookupCodeProviderDstu3 implements IValidationProviders.IMyLookupCodeProvider { - private UriType mySystemUrl; - private CodeType myCode; private LookupCodeResult myLookupCodeResult; @Override @@ -190,8 +188,6 @@ public IBaseParameters lookup( @OperationParam(name= " property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) { - myCode = theCode; - mySystemUrl = theSystem; if (theSystem == null) { throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode)); } @@ -205,15 +201,5 @@ public IBaseParameters lookup( public Class getResourceType() { return CodeSystem.class; } - - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java index 48a99f260d0b..4817542ef4dd 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyLookupCodeWithResponseFileDstu3Test.java @@ -5,13 +5,10 @@ import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersDstu3; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; -import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,13 +16,15 @@ import java.util.List; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_LOOKUP; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; public class RemoteTerminologyLookupCodeWithResponseFileDstu3Test { private static final FhirContext ourCtx = FhirContext.forDstu3Cached(); - private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider; + private IValidationProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider; @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); @@ -36,7 +35,7 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3(); + myCodeSystemProvider = new IValidationProvidersDstu3.MyCodeSystemProviderDstu3(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider); } @@ -47,13 +46,10 @@ public void after() { } @Test void lookupCode_withParametersOutput_convertsCorrectly() { - String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json"); - IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString); - assertTrue(baseResource instanceof Parameters); - Parameters resultParameters = (Parameters) baseResource; - myCodeSystemProvider.setReturnParams(resultParameters); + String outputFile ="/terminology/CodeSystem-lookup-output-with-subproperties.json"; + IBaseParameters resultParameters = myCodeSystemProvider.addTerminologyResponse(OPERATION_LOOKUP, CODE_SYSTEM, CODE, ourCtx, outputFile); - LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces")); + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces")); // test IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java index af4f39f09262..2f573ad3e5fc 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyValidateCodeDstu3Test.java @@ -4,16 +4,18 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersDstu3; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.jupiter.api.AfterEach; @@ -22,6 +24,11 @@ import java.util.List; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET; @@ -38,8 +45,8 @@ public class RemoteTerminologyValidateCodeDstu3Test implements IRemoteTerminolog private static final FhirContext ourCtx = FhirContext.forDstu3Cached(); @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); - private IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 myCodeSystemProvider; - private IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 myValueSetProvider; + private IValidationProviders.MyValidationProvider myCodeSystemProvider; + private IValidationProviders.MyValidationProvider myValueSetProvider; private RemoteTerminologyServiceValidationSupport mySvc; private String myCodeSystemError, myValueSetError; @@ -48,14 +55,14 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); myCodeSystemError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, CODE_SYSTEM, CODE, baseUrl, ERROR_MESSAGE); myValueSetError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, CODE_SYSTEM, CODE, VALUE_SET_URL, baseUrl, ERROR_MESSAGE); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3(); - myValueSetProvider = new IValidateCodeProvidersDstu3.MyValueSetProviderDstu3(); + myCodeSystemProvider = new IValidationProvidersDstu3.MyCodeSystemProviderDstu3(); + myValueSetProvider = new IValidationProvidersDstu3.MyValueSetProviderDstu3(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider); } @@ -82,45 +89,40 @@ public String getValueSetError() { } @Override - public IValidateCodeProvidersDstu3.MyCodeSystemProviderDstu3 getCodeSystemProvider() { + public IValidationProviders.IMyValidationProvider getCodeSystemProvider() { return myCodeSystemProvider; } @Override - public IValidateCodeProvidersDstu3.MyValueSetProviderDstu3 getValueSetProvider() { + public IValidationProviders.IMyValidationProvider getValueSetProvider() { return myValueSetProvider; } @Override public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json"); + return getCodeSystemInvalidCodeOutcome(OperationOutcome.class); } @Override public IBaseOperationOutcome getValueSetInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json"); + return getValueSetInvalidCodeOutcome(OperationOutcome.class); + } + + @Override + public IBaseOperationOutcome getValueSetCustomDetailCodeOutcome() { + return getValueSetCustomDetailCodeOutcome(OperationOutcome.class); } @Override public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { Parameters parameters = new Parameters(); parameters.addParameter().setName("result").setValue(new BooleanType(theResult)); - parameters.addParameter().setName("code").setValue(new StringType(IValidationProviders.CODE)); - parameters.addParameter().setName("system").setValue(new UriType(IValidationProviders.CODE_SYSTEM)); - parameters.addParameter().setName("version").setValue(new StringType(IValidationProviders.CODE_SYSTEM_VERSION)); + parameters.addParameter().setName("code").setValue(new StringType(CODE)); + parameters.addParameter().setName("system").setValue(new UriType(CODE_SYSTEM)); + parameters.addParameter().setName("version").setValue(new StringType(CODE_SYSTEM_VERSION)); parameters.addParameter().setName("display").setValue(new StringType(theDisplay)); parameters.addParameter().setName("message").setValue(new StringType(theMessage)); parameters.addParameter().setName("issues").setResource((Resource) theIssuesResource); return parameters; } - - @Override - public void createCodeSystemReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - myCodeSystemProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); - } - - @Override - public void createValueSetReturnParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { - myValueSetProvider.setReturnParams(createParameters(theResult, theDisplay, theMessage, theIssuesResource)); - } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index d03c3aa974d8..fb46f7f80089 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -307,10 +307,10 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message); } else { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java index d67966df6d46..382f621f547e 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeR4Test.java @@ -11,9 +11,10 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; import jakarta.servlet.http.HttpServletRequest; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyLookupCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseParameters; @@ -52,7 +53,7 @@ public class RemoteTerminologyLookupCodeR4Test implements IRemoteTerminologyLook @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private final RemoteTerminologyServiceValidationSupport mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); - private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; + private IValidationProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; private MyLookupCodeProviderR4 myLookupCodeProviderR4; @BeforeEach @@ -60,7 +61,7 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc.setBaseUrl(baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(true)); - myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); myLookupCodeProviderR4 = new MyLookupCodeProviderR4(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myLookupCodeProviderR4); } @@ -166,8 +167,6 @@ void lookupCode_withCodeSystemWithDesignation_returnsCorrectDesignation(final IV @SuppressWarnings("unused") static class MyLookupCodeProviderR4 implements IValidationProviders.IMyLookupCodeProvider { - private UriType mySystemUrl; - private CodeType myCode; private LookupCodeResult myLookupCodeResult; @Override @@ -192,8 +191,6 @@ public IBaseParameters lookup( @OperationParam(name = "property", max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) { - myCode = theCode; - mySystemUrl = theSystem; if (theSystem == null) { throw new InvalidRequestException(MessageFormat.format(MESSAGE_RESPONSE_INVALID, theCode)); } @@ -206,15 +203,5 @@ public IBaseParameters lookup( public Class getResourceType() { return CodeSystem.class; } - - @Override - public String getCode() { - return myCode != null ? myCode.getValueAsString() : null; - } - - @Override - public String getSystem() { - return mySystemUrl != null ? mySystemUrl.getValueAsString() : null; - } } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java index 37eba91d0ca1..a0896bd8c4e7 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyLookupCodeWithResponseFileR4Test.java @@ -5,12 +5,9 @@ import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -19,13 +16,15 @@ import java.util.List; +import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_LOOKUP; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; public class RemoteTerminologyLookupCodeWithResponseFileR4Test { private static final FhirContext ourCtx = FhirContext.forR4Cached(); - private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; + private IValidationProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); @@ -36,7 +35,7 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider); } @@ -48,13 +47,10 @@ public void after() { @Test void lookupCode_withParametersOutput_convertsCorrectly() { - String paramsAsString = ClasspathUtil.loadResource("/terminology/CodeSystem-lookup-output-with-subproperties.json"); - IBaseResource baseResource = ourCtx.newJsonParser().parseResource(paramsAsString); - assertTrue(baseResource instanceof Parameters); - Parameters resultParameters = (Parameters) baseResource; - myCodeSystemProvider.setReturnParams(resultParameters); + String outputFile ="/terminology/CodeSystem-lookup-output-with-subproperties.json"; + IBaseParameters resultParameters = myCodeSystemProvider.addTerminologyResponse(OPERATION_LOOKUP, CODE_SYSTEM, CODE, ourCtx, outputFile); - LookupCodeRequest request = new LookupCodeRequest(IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, null, List.of("interfaces")); + LookupCodeRequest request = new LookupCodeRequest(CODE_SYSTEM, CODE, null, List.of("interfaces")); // test IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, request); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java index 08f6c251869d..ffd8045a8a53 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/RemoteTerminologyValidateCodeR4Test.java @@ -2,8 +2,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; -import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.parser.IJsonLikeParser; import ca.uhn.fhir.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IHttpRequest; @@ -13,11 +13,11 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import ca.uhn.fhir.util.ClasspathUtil; +import ca.uhn.fhir.test.utilities.validation.IValidationProviders; +import ca.uhn.fhir.test.utilities.validation.IValidationProvidersR4; import ca.uhn.fhir.util.ParametersUtil; import com.google.common.collect.Lists; import org.hl7.fhir.common.hapi.validation.IRemoteTerminologyValidateCodeTest; -import org.hl7.fhir.common.hapi.validation.IValidationProviders; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -39,6 +39,12 @@ import java.util.List; import java.util.stream.Stream; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.CODE_SYSTEM_VERSION; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.DISPLAY; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.ERROR_MESSAGE; +import static ca.uhn.fhir.test.utilities.validation.IValidationProviders.VALUE_SET_URL; import static org.assertj.core.api.Assertions.assertThat; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM; import static org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport.ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET; @@ -61,8 +67,8 @@ public class RemoteTerminologyValidateCodeR4Test implements IRemoteTerminologyVa private static final FhirContext ourCtx = FhirContext.forR4Cached(); @RegisterExtension public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); - private IValidateCodeProvidersR4.MyCodeSystemProviderR4 myCodeSystemProvider; - private IValidateCodeProvidersR4.MyValueSetProviderR4 myValueSetProvider; + private IValidationProviders.IMyValidationProvider myCodeSystemProvider; + private IValidationProviders.IMyValidationProvider myValueSetProvider; private RemoteTerminologyServiceValidationSupport mySvc; private String myCodeSystemError, myValueSetError; @@ -71,14 +77,14 @@ public void before() { String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); myCodeSystemError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_CODE_SYSTEM, CODE_SYSTEM, CODE, baseUrl, ERROR_MESSAGE); myValueSetError = ourCtx.getLocalizer().getMessage( RemoteTerminologyServiceValidationSupport.class, - ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.VALUE_SET_URL, baseUrl, IValidationProviders.ERROR_MESSAGE); + ERROR_CODE_UNKNOWN_CODE_IN_VALUE_SET, CODE_SYSTEM, CODE, VALUE_SET_URL, baseUrl, ERROR_MESSAGE); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(false).setLogRequestSummary(true).setLogResponseSummary(true)); - myCodeSystemProvider = new IValidateCodeProvidersR4.MyCodeSystemProviderR4(); - myValueSetProvider = new IValidateCodeProvidersR4.MyValueSetProviderR4(); + myCodeSystemProvider = new IValidationProvidersR4.MyCodeSystemProviderR4(); + myValueSetProvider = new IValidationProvidersR4.MyValueSetProviderR4(); ourRestfulServerExtension.getRestfulServer().registerProviders(myCodeSystemProvider, myValueSetProvider); } @@ -95,12 +101,12 @@ public RemoteTerminologyServiceValidationSupport getService() { } @Override - public IValidationProviders.IMyCodeSystemProvider getCodeSystemProvider() { + public IValidationProviders.IMyValidationProvider getCodeSystemProvider() { return myCodeSystemProvider; } @Override - public IValidationProviders.IMyValueSetProvider getValueSetProvider() { + public IValidationProviders.IMyValidationProvider getValueSetProvider() { return myValueSetProvider; } @@ -116,51 +122,40 @@ public String getValueSetError() { @Override public IBaseOperationOutcome getCodeSystemInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-CodeSystem-invalid-code.json"); + return getCodeSystemInvalidCodeOutcome(OperationOutcome.class); } @Override public IBaseOperationOutcome getValueSetInvalidCodeOutcome() { - return ClasspathUtil.loadResource(getService().getFhirContext(), OperationOutcome.class, "/terminology/OperationOutcome-ValueSet-invalid-code.json"); + return getValueSetInvalidCodeOutcome(OperationOutcome.class); } @Override - public List getCodeValidationIssues(IBaseOperationOutcome theOperationOutcome) { - return ((OperationOutcome)theOperationOutcome).getIssue().stream() - .map(issueComponent -> new IValidationSupport.CodeValidationIssue( - issueComponent.getDetails().getText(), - IValidationSupport.IssueSeverity.ERROR, - /* assume issue type is OperationOutcome.IssueType#CODEINVALID as it is the only match */ - IValidationSupport.CodeValidationIssueCode.INVALID, - IValidationSupport.CodeValidationIssueCoding.INVALID_CODE)) - .toList(); + public IBaseOperationOutcome getValueSetCustomDetailCodeOutcome() { + return getValueSetCustomDetailCodeOutcome(OperationOutcome.class); } @Test void validateCodeInValueSet_success() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); - CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), IValidationProviders.CODE_SYSTEM, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), CODE_SYSTEM, CODE, DISPLAY, valueSet); assertNotNull(outcome); - assertEquals(IValidationProviders.CODE, outcome.getCode()); - assertEquals(IValidationProviders.DISPLAY, outcome.getDisplay()); + assertEquals(CODE, outcome.getCode()); + assertEquals(DISPLAY, outcome.getDisplay()); assertNull(outcome.getSeverity()); assertNull(outcome.getMessage()); - - assertEquals(IValidationProviders.CODE, myValueSetProvider.getCode()); - assertEquals(IValidationProviders.DISPLAY, myValueSetProvider.getDisplay()); - assertEquals(IValidationProviders.VALUE_SET_URL, myValueSetProvider.getValueSet()); } @Override public Parameters createParameters(Boolean theResult, String theDisplay, String theMessage, IBaseResource theIssuesResource) { Parameters parameters = new Parameters() - .addParameter("code", IValidationProviders.CODE) - .addParameter("system", IValidationProviders.CODE_SYSTEM) - .addParameter("version", IValidationProviders.CODE_SYSTEM_VERSION) + .addParameter("code", CODE) + .addParameter("system", CODE_SYSTEM) + .addParameter("version", CODE_SYSTEM_VERSION) .addParameter("display", theDisplay) .addParameter("message", theMessage); if (theResult != null) { @@ -181,16 +176,16 @@ class ExtractCodeSystemFromValueSet { @Test void validateCodeInValueSet_uniqueComposeInclude() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( Collections.singletonList(new ValueSet.ConceptSetComponent().setSystem(systemUrl)) )); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); // validate service doesn't return error message (as when no code system is present) assertNotNull(outcome); @@ -211,16 +206,16 @@ public static Stream getRemoteTerminologyServerExceptions() { @ParameterizedTest @MethodSource(value = "getRemoteTerminologyServerExceptions") void validateCodeInValueSet_systemNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) { - myValueSetProvider.setException(theException); - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + getValueSetProvider().addException("$validate-code", VALUE_SET_URL, CODE, theException); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( Lists.newArrayList(new ValueSet.ConceptSetComponent(), new ValueSet.ConceptSetComponent()))); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://"; verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage); @@ -230,11 +225,11 @@ void validateCodeInValueSet_systemNotPresent_returnsValidationResultWithError(Ex @ParameterizedTest @MethodSource(value = "getRemoteTerminologyServerExceptions") void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultWithError(Exception theException, String theServerMessage) { - myValueSetProvider.setException(theException); - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + getValueSetProvider().addException(JpaConstants.OPERATION_VALIDATE_CODE, VALUE_SET_URL, CODE, theException); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset"; valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( @@ -243,7 +238,7 @@ void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultW new ValueSet.ConceptSetComponent().setSystem(systemUrl2)))); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); String unknownCodeForValueSetError = "Unknown code \"null#CODE\" for ValueSet with URL \"http://value.set/url\". The Remote Terminology server http://"; verifyErrorResultFromException(outcome, unknownCodeForValueSetError, theServerMessage); @@ -252,10 +247,10 @@ void validateCodeInValueSet_systemPresentCodeNotPresent_returnsValidationResultW @Test void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset"; valueSet.setCompose(new ValueSet.ValueSetComposeComponent().setInclude( @@ -264,14 +259,14 @@ void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() { new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setConcept( Lists.newArrayList( new ValueSet.ConceptReferenceComponent().setCode("not-the-code"), - new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) ) + new ValueSet.ConceptReferenceComponent().setCode(CODE) ) )) )); TestClientInterceptor requestInterceptor = new TestClientInterceptor(); mySvc.addClientInterceptor(requestInterceptor); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); assertNotNull(outcome); assertEquals(systemUrl2, requestInterceptor.getCapturedSystemParameter()); @@ -280,10 +275,10 @@ void validateCodeInValueSet_systemPresentCodePresentValidatesOKNoVersioned() { @Test void validateCodeInValueSet_systemPresentCodePresentValidatesOKVersioned() { - createValueSetReturnParameters(true, IValidationProviders.DISPLAY, null, null); + createValueSetReturnParameters(true, DISPLAY, null, null); ValueSet valueSet = new ValueSet(); - valueSet.setUrl(IValidationProviders.VALUE_SET_URL); + valueSet.setUrl(VALUE_SET_URL); String systemUrl = "http://hl7.org/fhir/ValueSet/administrative-gender"; String systemVersion = "3.0.2"; String systemUrl2 = "http://hl7.org/fhir/ValueSet/other-valueset"; @@ -294,14 +289,14 @@ void validateCodeInValueSet_systemPresentCodePresentValidatesOKVersioned() { new ValueSet.ConceptSetComponent().setSystem(systemUrl2).setVersion(system2Version).setConcept( Lists.newArrayList( new ValueSet.ConceptReferenceComponent().setCode("not-the-code"), - new ValueSet.ConceptReferenceComponent().setCode(IValidationProviders.CODE) ) + new ValueSet.ConceptReferenceComponent().setCode(CODE) ) )) )); TestClientInterceptor requestInterceptor = new TestClientInterceptor(); mySvc.addClientInterceptor(requestInterceptor); CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, - new ConceptValidationOptions().setInferSystem(true), null, IValidationProviders.CODE, IValidationProviders.DISPLAY, valueSet); + new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); assertNotNull(outcome); assertEquals(systemUrl2 + "|" + system2Version, requestInterceptor.getCapturedSystemParameter()); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java index ca55a41bf4c2..d5035a8048ef 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java @@ -31,6 +31,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4b.conformance.ProfileUtilities; import org.hl7.fhir.r4b.context.IWorkerContext; +import org.hl7.fhir.r4b.fhirpath.FHIRPathEngine; import org.hl7.fhir.r4b.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4b.model.AllergyIntolerance; import org.hl7.fhir.r4b.model.Base; @@ -61,7 +62,6 @@ import org.hl7.fhir.r4b.model.ValueSet; import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r4b.terminologies.ValueSetExpander; -import org.hl7.fhir.r4b.fhirpath.FHIRPathEngine; import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; @@ -203,7 +203,7 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - return new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + return new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(message, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java index f0ea48686e17..abcb0f947046 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java @@ -48,7 +48,6 @@ import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; @@ -200,10 +199,10 @@ public IValidationSupport.CodeValidationResult answer(InvocationOnMock theInvoca retVal = new IValidationSupport.CodeValidationResult().setCode(code); } else if (myValidSystems.contains(system)) { String theMessage = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setCodeValidationIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(theMessage).setIssues(Collections.singletonList(new IValidationSupport.CodeValidationIssue(theMessage, IValidationSupport.IssueSeverity.ERROR, IValidationSupport.CodeValidationIssueCode.CODE_INVALID, IValidationSupport.CodeValidationIssueCoding.INVALID_CODE))); } else if (myValidSystemsNotReturningIssues.contains(system)) { final String message = "Unknown code (for '" + system + "#" + code + "')"; - retVal = new IValidationSupport.CodeValidationResult().setSeverityCode(ValidationMessage.IssueSeverity.ERROR.toCode()).setMessage(message); + retVal = new IValidationSupport.CodeValidationResult().setSeverity(IValidationSupport.IssueSeverity.ERROR).setMessage(message); } else { retVal = myDefaultValidationSupport.validateCode(new ValidationSupportContext(myDefaultValidationSupport), options, system, code, display, valueSetUrl); } diff --git a/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json new file mode 100644 index 000000000000..0823a430cf83 --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json @@ -0,0 +1,22 @@ +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "information", + "code": "processing", + "details": { + "coding": [ + { + "system": "http://example.com/custom-issue-type", + "code": "valueset-is-draft" + } + ] + }, + "diagnostics": "The ValueSet status is marked as draft.", + "location": [ + "Bundle", + "Line[1] Col[2]" + ] + } + ] +} \ No newline at end of file