diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index 5c3ce0bce..f4d5d76c3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -363,9 +363,13 @@ public class CodeSystemUtilities { } public static boolean isInactive(CodeSystem cs, ConceptDefinitionComponent def) throws FHIRException { + StandardsStatus ss = ToolingExtensions.getStandardsStatus(def); + if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) { + return true; + } for (ConceptPropertyComponent p : def.getProperty()) { if ("status".equals(p.getCode()) && p.hasValueStringType()) { - return "inactive".equals(p.getValueStringType().primitiveValue()) || "retired".equals(p.getValueStringType().primitiveValue()); + return "inactive".equals(p.getValueStringType().primitiveValue()) || "retired".equals(p.getValueStringType().primitiveValue()) || "deprecated".equals(p.getValueStringType().primitiveValue()); } if ("inactive".equals(p.getCode()) && p.hasValueBooleanType()) { return p.getValueBooleanType().getValue(); @@ -886,6 +890,10 @@ public class CodeSystemUtilities { public static DataType getProperty(CodeSystem cs, String code, String property) { ConceptDefinitionComponent def = getCode(cs, code); + return getProperty(cs, def, property); + } + + public static DataType getProperty(CodeSystem cs, ConceptDefinitionComponent def, String property) { ConceptPropertyComponent cp = getProperty(def, property); return cp == null ? null : cp.getValue(); } @@ -905,5 +913,18 @@ public class CodeSystemUtilities { } return false; } + + public static String getStatus(CodeSystem cs, ConceptDefinitionComponent cc) { + StandardsStatus ss = ToolingExtensions.getStandardsStatus(cc); + if (ss == StandardsStatus.DEPRECATED || ss == StandardsStatus.WITHDRAWN) { + return ss.toCode(); + } + DataType v = getProperty(cs, cc, "status"); + if (v == null || !v.isPrimitive()) { + return null; + } else { + return v.primitiveValue(); + } + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java index de31e7465..9ef1ddc1a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java @@ -13,12 +13,12 @@ import java.util.ArrayList; public class TerminologyOperationContext { - public class TerminologyServiceProtectionException extends FHIRException { + public static class TerminologyServiceProtectionException extends FHIRException { private TerminologyServiceErrorClass error; private IssueType type; - protected TerminologyServiceProtectionException(String message, TerminologyServiceErrorClass error, IssueType type) { + public TerminologyServiceProtectionException(String message, TerminologyServiceErrorClass error, IssueType type) { super(message); this.error = error; this.type = type; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java index e1e8fbd0c..d650de477 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java @@ -143,7 +143,7 @@ public class ValueSetProcessBase { } private List makeStatusIssue(String path, String id, String msg, CanonicalResource resource) { - List iss = makeIssue(IssueSeverity.INFORMATION, IssueType.EXPIRED, path, context.formatMessage(msg, resource.getVersionedUrl())); + List iss = makeIssue(IssueSeverity.INFORMATION, IssueType.BUSINESSRULE, path, context.formatMessage(msg, resource.getVersionedUrl())); // this is a testing hack - see TerminologyServiceTests iss.get(0).setUserData("status-msg-name", "warning-"+id); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java index 3b8734217..54cb87cbb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java @@ -24,7 +24,7 @@ public class ValidationProcessInfo { public void setErr(TerminologyServiceErrorClass err) { this.err = err; } - + public List getIssues() { return issues; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java index 55bf7eebb..a67eb311c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java @@ -81,6 +81,7 @@ import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem; import org.hl7.fhir.r5.terminologies.providers.URICodeSystem; import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase; import org.hl7.fhir.r5.utils.ToolingExtensions; @@ -195,6 +196,7 @@ public class ValueSetValidator extends ValueSetProcessBase { ValidationProcessInfo info = new ValidationProcessInfo(); CodeableConcept vcc = new CodeableConcept(); + if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { int i = 0; for (Coding c : code.getCoding()) { @@ -262,6 +264,9 @@ public class ValueSetValidator extends ValueSetProcessBase { info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path, msg)); } } + if (vcc.hasCoding() && code.hasText()) { + vcc.setText(code.getText()); + } if (!checkRequiredSupplements(info)) { return new ValidationResult(IssueSeverity.ERROR, info.getIssues().get(info.getIssues().size()-1).getDetails().getText(), info.getIssues()); } else if (info.hasErrors()) { @@ -295,7 +300,8 @@ public class ValueSetValidator extends ValueSetProcessBase { private boolean checkRequiredSupplements(ValidationProcessInfo info) { if (!requiredSupplements.isEmpty()) { - info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, null, context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)))); + String msg= context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)); + throw new TerminologyServiceProtectionException(msg, TerminologyServiceErrorClass.BUSINESS_RULE, IssueType.NOTFOUND); } return requiredSupplements.isEmpty(); } @@ -529,6 +535,8 @@ public class ValueSetValidator extends ValueSetProcessBase { res.setMessage("Code found in include, however: " + res.getMessage()); res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.EXCEPTION, path, res.getMessage())); } + } else if (res == null) { + res = new ValidationResult(system, wv, null, null); } } else if ((res != null && !res.isOk())) { String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), code.toString()); @@ -676,18 +684,19 @@ public class ValueSetValidator extends ValueSetProcessBase { if (vcc != null) { vcc.addCoding(vc); } - if (CodeSystemUtilities.isInactive(cs, cc)) { - info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.EXPIRED, path, context.formatMessage(I18nConstants.INACTIVE_CODE_WARNING, cc.getCode()))); - } - boolean ws = false; + + boolean inactive = (CodeSystemUtilities.isInactive(cs, cc)); + String status = inactive ? (CodeSystemUtilities.getStatus(cs, cc)) : null; + + boolean ws = false; if (code.getDisplay() == null) { - return new ValidationResult(code.getSystem(), cs.getVersion(), cc, vc.getDisplay()); + return new ValidationResult(code.getSystem(), cs.getVersion(), cc, vc.getDisplay()).setStatus(inactive, status); } CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", ", " or "); if (cc.hasDisplay() && isOkLanguage(cs.getLanguage())) { b.append("'"+cc.getDisplay()+"'"); if (code.getDisplay().equalsIgnoreCase(cc.getDisplay())) { - return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)); + return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); } else if (Utilities.normalize(code.getDisplay()).equals(Utilities.normalize(cc.getDisplay()))) { ws = true; } @@ -698,7 +707,7 @@ public class ValueSetValidator extends ValueSetProcessBase { if (isOkLanguage(ds.getLanguage())) { b.append("'"+ds.getValue()+"'"); if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { - return new ValidationResult(code.getSystem(),cs.getVersion(), cc, getPreferredDisplay(cc, cs)); + return new ValidationResult(code.getSystem(),cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); } if (Utilities.normalize(code.getDisplay()).equalsIgnoreCase(Utilities.normalize(ds.getValue()))) { ws = true; @@ -712,7 +721,7 @@ public class ValueSetValidator extends ValueSetProcessBase { if (vs.getCc().hasDisplay() && isOkLanguage(vs.getValueset().getLanguage())) { b.append("'"+vs.getCc().getDisplay()+"'"); if (code.getDisplay().equalsIgnoreCase(vs.getCc().getDisplay())) { - return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)); + return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); } } for (ConceptReferenceDesignationComponent ds : vs.getCc().getDesignation()) { @@ -720,7 +729,7 @@ public class ValueSetValidator extends ValueSetProcessBase { if (isOkLanguage(ds.getLanguage())) { b.append("'"+ds.getValue()+"'"); if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { - return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)); + return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); } } } @@ -728,10 +737,10 @@ public class ValueSetValidator extends ValueSetProcessBase { } if (b.count() == 0) { String msg = context.formatMessagePlural(options.getLanguages().size(), I18nConstants.NO_VALID_DISPLAY_FOUND, code.getSystem(), code.getCode(), code.getDisplay(), options.langSummary()); - return new ValidationResult(IssueSeverity.WARNING, msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".display", msg)); + return new ValidationResult(IssueSeverity.WARNING, msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".display", msg)).setStatus(inactive, status); } else { String msg = context.formatMessagePlural(b.count(), ws ? I18nConstants.DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF : I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF, code.getSystem(), code.getCode(), b.toString(), code.getDisplay(), options.langSummary()); - return new ValidationResult(dispWarningStatus(), msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(dispWarning(), IssueType.INVALID, path+".display", msg)); + return new ValidationResult(dispWarningStatus(), msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(dispWarning(), IssueType.INVALID, path+".display", msg)).setStatus(inactive, status); } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java index c597332fa..060c8f581 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java @@ -255,7 +255,7 @@ public class TxTester { TxTesterSorters.sortValueSet(vs); vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vs); } catch (EFhirClientException e) { - OperationOutcome oo = e.getServerErrors().get(0); + OperationOutcome oo = e.getServerError(); TxTesterScrubbers.scrubOO(oo, tight); vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); } @@ -279,7 +279,7 @@ public class TxTester { TxTesterSorters.sortParameters(po); pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po); } catch (EFhirClientException e) { - OperationOutcome oo = e.getServerErrors().get(0); + OperationOutcome oo = e.getServerError(); oo.setText(null); pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java index f8ba62695..a2473049e 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java @@ -29,8 +29,12 @@ public class TxTesterSorters { } } } - + + public static void sortOperationOutcome(OperationOutcome oo) { + Collections.sort(oo.getIssue(), new TxTesterSorters.OperationIssueSorter()); + } + public static void sortValueSet(ValueSet vs) { Collections.sort(vs.getExtension(), new TxTesterSorters.ExtensionSorter()); if (vs.hasExpansion()) { diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java index f2a732704..36ee09822 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java @@ -78,7 +78,7 @@ public class TerminologyServiceTests { String contents = TestingUtilities.loadTestResource("tx", "test-cases.json"); String externalSource = TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json"); externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(externalSource); - + Map examples = new HashMap(); manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents); for (org.hl7.fhir.utilities.json.model.JsonObject suite : manifest.getJsonObjects("suites")) { @@ -95,7 +95,7 @@ public class TerminologyServiceTests { List objects = new ArrayList(examples.size()); for (String id : names) { - objects.add(new Object[]{id, examples.get(id)}); + objects.add(new Object[]{id, examples.get(id)}); } return objects; } @@ -105,7 +105,7 @@ public class TerminologyServiceTests { private JsonObjectPair setup; private String version; private String name; - + private static ValidationEngine baseEngine; @@ -114,7 +114,7 @@ public class TerminologyServiceTests { this.setup = setup; version = "5.0.0"; } - + @SuppressWarnings("deprecation") @Test public void test() throws Exception { @@ -208,8 +208,9 @@ public class TerminologyServiceTests { } e.getDetails().setText(vse.getError()); oo.addIssue(e); + TxTesterSorters.sortOperationOutcome(oo); TxTesterScrubbers.scrubOO(oo, false); - + String ooj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); String diff = CompareUtilities.checkJsonSrcIsSame(resp, ooj, ext); if (diff != null) { @@ -242,7 +243,7 @@ public class TerminologyServiceTests { options = options.withLanguage(p.getParameterString("displayLanguage")); } if (p.hasParameter("valueSetMode") && "CHECK_MEMBERSHIP_ONLY".equals(p.getParameterString("valueSetMode"))) { - options = options.withCheckValueSetOnly(); + options = options.withCheckValueSetOnly(); } if (p.hasParameter("mode") && "lenient-display-validation".equals(p.getParameterString("mode"))) { options = options.setDisplayWarningMode(true); @@ -265,51 +266,73 @@ public class TerminologyServiceTests { } else { throw new Error("validate not done yet for this steup"); } - org.hl7.fhir.r5.model.Parameters res = new org.hl7.fhir.r5.model.Parameters(); - if (vm.getSystem() != null) { - res.addParameter("system", new UriType(vm.getSystem())); - } - if (vm.getCode() != null) { - res.addParameter("code", new CodeType(vm.getCode())); - } - if (vm.getSeverity() == org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity.ERROR) { - res.addParameter("result", false); - } else { - res.addParameter("result", true); - } - if (vm.getMessage() != null) { - res.addParameter("message", vm.getMessage()); - } - if (vm.getVersion() != null) { - res.addParameter("version", vm.getVersion()); - } - if (vm.getDisplay() != null) { - res.addParameter("display", vm.getDisplay()); - } - if (vm.getCodeableConcept() != null) { - res.addParameter("codeableConcept", vm.getCodeableConcept()); - } - if (vm.getUnknownSystems() != null) { - for (String s : vm.getUnknownSystems()) { - res.addParameter("x-caused-by-unknown-system", new CanonicalType(s)); - } - } - if (vm.getIssues().size() > 0) { + if (vm.getSeverity() == org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity.FATAL) { OperationOutcome oo = new OperationOutcome(); oo.getIssue().addAll(vm.getIssues()); - res.addParameter().setName("issues").setResource(oo); + TxTesterSorters.sortOperationOutcome(oo); + TxTesterScrubbers.scrubOO(oo, false); + + String pj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext); + if (diff != null) { + Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); + TextFile.stringToFile(pj, fp); + System.out.println("Test "+name+"failed: "+diff); + } + Assertions.assertTrue(diff == null, diff); + } else { + org.hl7.fhir.r5.model.Parameters res = new org.hl7.fhir.r5.model.Parameters(); + if (vm.getSystem() != null) { + res.addParameter("system", new UriType(vm.getSystem())); + } + if (vm.getCode() != null) { + res.addParameter("code", new CodeType(vm.getCode())); + } + if (vm.getSeverity() == org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity.ERROR) { + res.addParameter("result", false); + } else { + res.addParameter("result", true); + } + if (vm.getMessage() != null) { + res.addParameter("message", vm.getMessage()); + } + if (vm.getVersion() != null) { + res.addParameter("version", vm.getVersion()); + } + if (vm.getDisplay() != null) { + res.addParameter("display", vm.getDisplay()); + } + if (vm.getCodeableConcept() != null) { + res.addParameter("codeableConcept", vm.getCodeableConcept()); + } + if (vm.isInactive()) { + res.addParameter("inactive", true); + } + if (vm.getStatus() != null) { + res.addParameter("status", vm.getStatus()); + } + if (vm.getUnknownSystems() != null) { + for (String s : vm.getUnknownSystems()) { + res.addParameter("x-caused-by-unknown-system", new CanonicalType(s)); + } + } + if (vm.getIssues().size() > 0) { + OperationOutcome oo = new OperationOutcome(); + oo.getIssue().addAll(vm.getIssues()); + res.addParameter().setName("issues").setResource(oo); + } + TxTesterSorters.sortParameters(res); + TxTesterScrubbers.scrubParams(res); + + String pj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(res); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext); + if (diff != null) { + Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); + TextFile.stringToFile(pj, fp); + System.out.println("Test "+name+"failed: "+diff); + } + Assertions.assertTrue(diff == null, diff); } - TxTesterSorters.sortParameters(res); - TxTesterScrubbers.scrubParams(res); - - String pj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(res); - String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext); - if (diff != null) { - Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); - TextFile.stringToFile(pj, fp); - System.out.println("Test "+name+"failed: "+diff); - } - Assertions.assertTrue(diff == null, diff); } public Resource loadResource(String filename) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {