This commit is contained in:
Grahame Grieve 2024-05-10 08:49:11 +10:00
commit 5259bf9847
16 changed files with 521 additions and 74 deletions

View File

@ -4,4 +4,5 @@
## Other code changes
* no changes
* More comprehensive internationalization phrase coverage reporting.
* Shim interfaces and classes to support clinical reasoning project updates.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,5 +1,5 @@
Locale,Coverage #,Coverage %
de,869,43%
es,740,37%
es,740,36%
ja,935,46%
nl,873,43%

1 Locale Coverage # Coverage %
2 de 869 43%
3 es 740 37% 36%
4 ja 935 46%
5 nl 873 43%

View File

@ -277,7 +277,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
protected boolean canRunWithoutTerminology;
protected boolean noTerminologyServer;
private int expandCodesLimit = 1000;
protected ILoggingService logger = new SystemOutLoggingService();
protected org.hl7.fhir.r5.context.ILoggingService logger = new SystemOutLoggingService();
protected Parameters expParameters;
private Map<String, PackageInformation> packages = new HashMap<>();
@ -1991,7 +1991,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
this.canRunWithoutTerminology = canRunWithoutTerminology;
}
public void setLogger(@Nonnull ILoggingService logger) {
public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger) {
this.logger = logger;
}
@ -2816,7 +2816,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
@Override
public ILoggingService getLogger() {
public org.hl7.fhir.r5.context.ILoggingService getLogger() {
return logger;
}

View File

@ -109,6 +109,18 @@ import javax.annotation.Nonnull;
public interface IWorkerContext {
/**
@deprecated This interface only exists to provide backward compatibility for the following two projects:
<a href="https://github.com/cqframework/clinical-reasoning">clinical-reasoning</a>
<a href="https://github.com/cqframework/clinical_quality_language/">clinical_quality-language</a>
Due to a circular dependency, they cannot be updated without a release of HAPI, which requires backwards
compatibility with core version 6.1.2.2
**/
@Deprecated(forRemoval = true)
public interface ILoggingService extends org.hl7.fhir.r5.context.ILoggingService{
}
public class OIDDefinitionComparer implements Comparator<OIDDefinition> {
@Override
@ -630,8 +642,8 @@ public interface IWorkerContext {
// todo: figure these out
public Map<String, NamingSystem> getNSUrlMap();
public void setLogger(@Nonnull ILoggingService logger);
public ILoggingService getLogger();
public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger);
public org.hl7.fhir.r5.context.ILoggingService getLogger();
public boolean isNoTerminologyServer();
public Set<String> getCodeSystemsUsed();

View File

@ -214,7 +214,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
private final boolean allowLoadingDuplicates;
@With
private final ILoggingService loggingService;
private final org.hl7.fhir.r5.context.ILoggingService loggingService;
public SimpleWorkerContextBuilder() {
cacheTerminologyClientErrors = false;

View File

@ -0,0 +1,228 @@
package org.hl7.fhir.r5.model;
import org.hl7.fhir.r5.fhirpath.TypeDetails;
import org.hl7.fhir.utilities.SourceLocation;
import java.util.List;
/**
@deprecated This interface only exists to provide backward compatibility for the following two projects:
<a href="https://github.com/cqframework/clinical-reasoning">clinical-reasoning</a>
<a href="https://github.com/cqframework/clinical_quality_language/">clinical_quality-language</a>
Due to a circular dependency, they cannot be updated without a release of HAPI, which requires backwards
compatibility with core version 6.1.2.2
**/
public class ExpressionNode extends org.hl7.fhir.r5.fhirpath.ExpressionNode{
private final org.hl7.fhir.r5.fhirpath.ExpressionNode wrappedExpressionNode;
public ExpressionNode(int uniqueId) {
super(0);
wrappedExpressionNode = new org.hl7.fhir.r5.fhirpath.ExpressionNode(uniqueId);
}
public ExpressionNode(org.hl7.fhir.r5.fhirpath.ExpressionNode wrappedExpressionNode) {
super(0);
this.wrappedExpressionNode = wrappedExpressionNode;
}
@Override
public String toString() {
return wrappedExpressionNode.toString();
}
@Override
public String getName() {
return wrappedExpressionNode.getName();
}
@Override
public void setName(String name) {
wrappedExpressionNode.setName(name);
}
@Override
public Base getConstant() {
return wrappedExpressionNode.getConstant();
}
@Override
public void setConstant(Base constant) {
wrappedExpressionNode.setConstant(constant);
}
@Override
public Function getFunction() {
return wrappedExpressionNode.getFunction();
}
@Override
public void setFunction(Function function) {
wrappedExpressionNode.setFunction(function);
}
@Override
public boolean isProximal() {
return wrappedExpressionNode.isProximal();
}
@Override
public void setProximal(boolean proximal) {
wrappedExpressionNode.setProximal(proximal);
}
@Override
public Operation getOperation() {
return wrappedExpressionNode.getOperation();
}
@Override
public void setOperation(Operation operation) {
wrappedExpressionNode.setOperation(operation);
}
@Override
public org.hl7.fhir.r5.fhirpath.ExpressionNode getInner() {
return wrappedExpressionNode.getInner();
}
@Override
public void setInner(org.hl7.fhir.r5.fhirpath.ExpressionNode value) {
wrappedExpressionNode.setInner(value);
}
@Override
public org.hl7.fhir.r5.fhirpath.ExpressionNode getOpNext() {
return wrappedExpressionNode.getOpNext();
}
@Override
public void setOpNext(org.hl7.fhir.r5.fhirpath.ExpressionNode value) {
wrappedExpressionNode.setOpNext(value);
}
@Override
public List<org.hl7.fhir.r5.fhirpath.ExpressionNode> getParameters() {
return wrappedExpressionNode.getParameters();
}
@Override
public boolean checkName() {
return wrappedExpressionNode.checkName();
}
@Override
public Kind getKind() {
return wrappedExpressionNode.getKind();
}
@Override
public void setKind(Kind kind) {
wrappedExpressionNode.setKind(kind);
}
@Override
public org.hl7.fhir.r5.fhirpath.ExpressionNode getGroup() {
return wrappedExpressionNode.getGroup();
}
@Override
public void setGroup(org.hl7.fhir.r5.fhirpath.ExpressionNode group) {
wrappedExpressionNode.setGroup(group);
}
@Override
public SourceLocation getStart() {
return wrappedExpressionNode.getStart();
}
@Override
public void setStart(SourceLocation start) {
wrappedExpressionNode.setStart(start);
}
@Override
public SourceLocation getEnd() {
return wrappedExpressionNode.getEnd();
}
@Override
public void setEnd(SourceLocation end) {
wrappedExpressionNode.setEnd(end);
}
@Override
public SourceLocation getOpStart() {
return wrappedExpressionNode.getOpStart();
}
@Override
public void setOpStart(SourceLocation opStart) {
wrappedExpressionNode.setOpStart(opStart);
}
@Override
public SourceLocation getOpEnd() {
return wrappedExpressionNode.getOpEnd();
}
@Override
public void setOpEnd(SourceLocation opEnd) {
wrappedExpressionNode.setOpEnd(opEnd);
}
@Override
public String getUniqueId() {
return wrappedExpressionNode.getUniqueId();
}
@Override
public int parameterCount() {
return wrappedExpressionNode.parameterCount();
}
@Override
public String Canonical() {
return wrappedExpressionNode.Canonical();
}
@Override
public String summary() {
return wrappedExpressionNode.summary();
}
@Override
public String check() {
return wrappedExpressionNode.check();
}
@Override
public TypeDetails getTypes() {
return wrappedExpressionNode.getTypes();
}
@Override
public void setTypes(TypeDetails types) {
wrappedExpressionNode.setTypes(types);
}
@Override
public TypeDetails getOpTypes() {
return wrappedExpressionNode.getOpTypes();
}
@Override
public void setOpTypes(TypeDetails opTypes) {
wrappedExpressionNode.setOpTypes(opTypes);
}
@Override
public List<String> getDistalNames() {
return wrappedExpressionNode.getDistalNames();
}
@Override
public boolean isNullSet() {
return wrappedExpressionNode.isNullSet();
}
}

View File

@ -0,0 +1,30 @@
package org.hl7.fhir.r5.utils;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.Base;
import java.util.List;
/**
@deprecated This interface only exists to provide backward compatibility for the following two projects:
<a href="https://github.com/cqframework/clinical-reasoning">clinical-reasoning</a>
<a href="https://github.com/cqframework/clinical_quality_language/">clinical_quality-language</a>
Due to a circular dependency, they cannot be updated without a release of HAPI, which requires backwards
compatibility with core version 6.1.2.2
**/
public class FHIRPathEngine extends org.hl7.fhir.r5.fhirpath.FHIRPathEngine {
public interface IEvaluationContext extends org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext{ }
public FHIRPathEngine(IWorkerContext worker) {
super(worker);
}
public org.hl7.fhir.r5.model.ExpressionNode parse(String string) {
return new org.hl7.fhir.r5.model.ExpressionNode(super.parse(string));
}
public List<Base> evaluate(Base base, org.hl7.fhir.r5.model.ExpressionNode expressionNode) {
return super.evaluate(base, expressionNode);
}
}

View File

@ -1,26 +1,82 @@
package org.hl7.fhir.utilities;
import org.apache.commons.lang3.StringUtils;
/**
* Abstraction that splits a canonical in form of {@code <url>|<version>} into a URL and a version part.
*/
public class CanonicalPair {
private String url;
private String version;
private final String url;
private final String version;
/**
* Static factory method, that invokes the {@link CanonicalPair#CanonicalPair(String) CanonicalPair constructor}
* with the given argument.
* @param target the canonical to be split. May be {@code null}.
* @return new instance of CanonicalPair
*/
public static CanonicalPair of(String target) {
return new CanonicalPair(target);
}
/**
* Wraps the given canonical and if needed splits off the version part.<p>
* The given parameter {@code target} is expected to be a canonical.
* @param target canonical to create a url, version pair from. May be {@code null}.
*/
public CanonicalPair(String target) {
if (target != null && target.contains("|")) {
this.url = target.substring(0, target.indexOf("|"));
this.version = target.substring(target.indexOf("|")+1);
int pipeIndex = target != null ? target.indexOf('|') : -1;
if (pipeIndex >= 0) {
this.url = target.substring(0, pipeIndex);
this.version = target.substring(pipeIndex+1);
} else {
this.url = target;
this.version = null;
}
}
/**
* Returns the URL part of the canonical (everything before the {@code "|"} character, or the complete
* canonical if the character is not present). If the source
* @return URL part of the source canonical. May be {@code null}, if source canonical was {@code null}
*/
public String getUrl() {
return url;
}
/**
* Returns the version part of the source canonical (everything after the {@code "|"} character.
* @return version part of the canonical, may be {@code null}, if canonical was {@code null}, or canonical contains no
* {@code "|"} character.
*/
public String getVersion() {
return version;
}
/**
* Determines if the version part of the canonical is not {@code null}
* @return {@code true} if version is not {@code null}, otherwise {@code false}
*/
public boolean hasVersion() {
return version != null;
}
/**
* Returns the version of this pair, or the parameter {@code alternative},
* if the version of this pair is {@code null}.
* @param alternative to be returned from this method if the encapsulated version is {@code null}.
* @return either the held version, or {@code alternative}, if version is {@code null}
*/
public String getVersionOr(String alternative) {
return hasVersion() ? version : alternative;
}
/**
* Determines if the encapsulated version of this pair is not {@code null} and not an empty string.
* @return {@code true} if the version of this pair is not {@code null} and not an empty string, {@code false} otherwise
*/
public boolean hasNonEmptyVersion() {
return StringUtils.isNotEmpty(version);
}
}

View File

@ -1,7 +1,7 @@
package org.hl7.fhir.utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
package org.hl7.fhir.utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
@ -30,21 +30,23 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
POSSIBILITY OF SUCH DAMAGE.
*/
/**
* This class is superceded by TerminologyValidationOptions but retained here for backwards compatibility
* @author graha
*
*/
public class TerminologyServiceOptions extends ValidationOptions {
public TerminologyServiceOptions(FhirPublication fhirVersion) {
super(fhirVersion);
}
public TerminologyServiceOptions(FhirPublication fhirVersion, String lang) {
super(fhirVersion, lang);
}
/**
* This class is superceded by TerminologyValidationOptions but retained here for backwards compatibility
* @author graha
*
*/
public class TerminologyServiceOptions extends ValidationOptions {
public TerminologyServiceOptions() { this(FhirPublication.R5); }
public TerminologyServiceOptions(FhirPublication fhirVersion) {
super(fhirVersion);
}
public TerminologyServiceOptions(FhirPublication fhirVersion, String lang) {
super(fhirVersion, lang);
}
}

View File

@ -25,6 +25,8 @@ public class ValidationOptions {
private boolean exampleOK = false;
private FhirPublication fhirVersion;
public ValidationOptions() { this(FhirPublication.R5); }
public ValidationOptions(FhirPublication fhirVersion) {
super();
this.fhirVersion = fhirVersion;

View File

@ -0,0 +1,90 @@
package org.hl7.fhir.utilities;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class CanonicalPairTest {
@Test
void testCanonicalNull() {
var canonical = new CanonicalPair(null);
assertNull(canonical.getUrl());
assertNull(canonical.getVersion());
assertFalse(canonical.hasVersion());
assertFalse(canonical.hasNonEmptyVersion());
String expectedVer = "1.0";
String actualVer = canonical.getVersionOr(expectedVer);
assertEquals(expectedVer, actualVer);
}
@Test
void testCanonicalEmpty() {
var url = "";
var canonical = new CanonicalPair(url);
assertEquals(url, canonical.getUrl());
assertFalse(canonical.hasVersion());
assertFalse(canonical.hasNonEmptyVersion());
String expectedVer = "1.0";
String actualVer = canonical.getVersionOr(expectedVer);
assertEquals(expectedVer, actualVer);
}
@Test
void testCanonicalWithoutVersion() {
var url = "https://www.test.org";
var canonical = new CanonicalPair(url);
assertEquals(url, canonical.getUrl());
assertNull(canonical.getVersion());
assertFalse(canonical.hasVersion());
assertFalse(canonical.hasNonEmptyVersion());
String expectedVer = "1.0";
String actualVer = canonical.getVersionOr(expectedVer);
assertEquals(expectedVer, actualVer);
}
@Test
void testCanonicalWithEmptyVersion() {
var expectedUrl = "https://www.test.org";
var url = expectedUrl + "|";
var canonical = new CanonicalPair(url);
assertEquals(expectedUrl, canonical.getUrl());
assertEquals("", canonical.getVersion());
assertTrue(canonical.hasVersion());
assertFalse(canonical.hasNonEmptyVersion());
String alternativeVer = "1.0";
String actualVer = canonical.getVersionOr(alternativeVer);
assertEquals("", actualVer);
}
@Test
void testCanonicalWithVersion() {
var expectedUrl = "https://www.test.org";
var expectedVersion = "2.6";
var url = expectedUrl + "|" + expectedVersion;
var canonical = new CanonicalPair(url);
assertEquals(expectedUrl, canonical.getUrl());
assertEquals(expectedVersion, canonical.getVersion());
assertTrue(canonical.hasVersion());
assertTrue(canonical.hasNonEmptyVersion());
String alternativeVer = "1.0";
String actualVer = canonical.getVersionOr(alternativeVer);
assertEquals(expectedVersion, actualVer);
}
@Test
void testCanonicalWithVersionIncludingPipe() {
var expectedUrl = "https://www.test.org";
var expectedVersion = "2024|05";
var url = expectedUrl + "|" + expectedVersion;
var canonical = new CanonicalPair(url);
assertEquals(expectedUrl, canonical.getUrl());
assertEquals(expectedVersion, canonical.getVersion());
assertTrue(canonical.hasVersion());
assertTrue(canonical.hasNonEmptyVersion());
String alternativeVer = "1.0";
String actualVer = canonical.getVersionOr(alternativeVer);
assertEquals(expectedVersion, actualVer);
}
}

View File

@ -1428,25 +1428,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
return null;
}
protected String versionFromCanonical(String system) {
if (system == null) {
return null;
} else if (system.contains("|")) {
return system.substring(0, system.indexOf("|"));
} else {
return system;
}
}
protected String systemFromCanonical(String system) {
if (system == null) {
return null;
} else if (system.contains("|")) {
return system.substring(system.indexOf("|")+1);
} else {
return system;
}
}
@Override
public Resource loadContainedResource(List<ValidationMessage> errors, String path, Element resource, String id, Class<? extends Resource> class1) throws FHIRException {

View File

@ -1456,9 +1456,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
timeTracker.tx(t, "vc "+cc.toString());
}
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage());
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage());
}
}
} else if (binding.hasValueSet()) {
@ -1469,12 +1469,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
if (!noTerminologyChecks && theElementCntext != null && !checked) { // no binding check, so we just check the CodeableConcept generally
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
if (cc.hasCoding()) {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, null, cc);
bh.see(processTxIssues(errors, vr, element, path, org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, false, null));
timeTracker.tx(t, "vc "+cc.toString());
try {
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
if (cc.hasCoding()) {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, null, cc);
bh.see(processTxIssues(errors, vr, element, path, org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, false, null));
timeTracker.tx(t, "vc " + cc.toString());
}
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage());
}
}
return checkDisp;
@ -1502,8 +1507,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean ok = true;
if (vr != null) {
for (OperationOutcomeIssueComponent iss : vr.getIssues()) {
if (!iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-in-vs") &&
!iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "this-code-not-in-vs")
if (!iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-in-vs")
&& !iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "this-code-not-in-vs")
&& !(ignoreCantInfer || iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "cannot-infer"))) {
OperationOutcomeIssueComponent i = iss.copy();
if (notFoundLevel != null && i.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-found")) {
@ -1653,9 +1658,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
timeTracker.tx(t, DataRenderer.display(context, cc));
}
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage());
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage());
}
// special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding.
if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) {
@ -1861,9 +1866,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
else
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset), ccSummary(cc)) && ok;
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage());
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getCause().getMessage());
}
}
return ok;
@ -7474,6 +7479,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return checkForInactive(filterOutSpecials(stack.getLiteralPath(), vs, context.validateCode(options, value, vs)), new CodeType(value));
}
static class CheckCodeOnServerException extends Exception {
public CheckCodeOnServerException(Exception e) {
super(e);
}
}
// no delay on this one?
public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) {
@ -7490,9 +7500,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return checkForInactive(filterOutSpecials(stack.getLiteralPath(), valueset, context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()), c, valueset)), c);
}
public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc) {
public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc) throws CheckCodeOnServerException {
codingObserver.seeCode(stack, cc);
return checkForInactive(filterOutSpecials(stack.getLiteralPath(), valueset, context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()), cc, valueset)), cc);
try {
return checkForInactive(filterOutSpecials(stack.getLiteralPath(), valueset, context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()), cc, valueset)), cc);
} catch (Exception e) {
throw new CheckCodeOnServerException(e);
}
}
private ValidationResult filterOutSpecials(String path, ValueSet vs, ValidationResult vr) {

View File

@ -13,6 +13,7 @@ import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.utilities.CanonicalPair;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
@ -620,7 +621,8 @@ public class CodeSystemValidator extends BaseValidator {
private boolean validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) {
String code = concept.getChildValue("code");
if (!Utilities.noString(code)) {
org.hl7.fhir.r5.terminologies.utilities.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null);
var canonical = new CanonicalPair(supp);
org.hl7.fhir.r5.terminologies.utilities.ValidationResult res = context.validateCode(options, canonical.getUrl(), canonical.getVersion(), code, null);
return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code);
} else {
return true;

View File

@ -1,5 +1,9 @@
package org.hl7.fhir.validation;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
@ -10,6 +14,7 @@ import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.instance.InstanceValidator;
@ -25,7 +30,7 @@ public class ResourceValidationTests {
private static InstanceValidator val;
private void runTest(String filename) throws IOException, FileNotFoundException, Exception {
private List<ValidationMessage> runTest(String filename) throws IOException, FileNotFoundException, Exception {
TestingUtilities.injectCorePackageLoader();
if (val == null) {
ctxt = TestingUtilities.getSharedWorkerContext();
@ -37,6 +42,7 @@ public class ResourceValidationTests {
Resource res = (Resource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", filename));
val.validate(val, errors, res);
Assertions.assertNotNull(errors);
return errors;
}
@ -123,4 +129,26 @@ public class ResourceValidationTests {
}
}
@Test
public void testCodesystemSupplementsVersion() throws Exception {
List<ValidationMessage> errors = runTest("codesystem-example-supplement-version.xml");
assertNoErrors(errors);
}
private void assertNoErrors(List<ValidationMessage> errors) {
List<String> errorMessages = new ArrayList<>();
for(ValidationMessage message : errors) {
// we will skip the message that WG citation is needed
if(I18nConstants.VALIDATION_HL7_WG_NEEDED.equals(message.getMessageId())) {
continue;
}
if(message.getLevel().isError()) {
errorMessages.add(message.getMessage());
}
}
assertThat("No error message expected in validation outcome.", errorMessages, is(empty()));
}
}