diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7b06c6ab5..13ab15751 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,12 @@ ## Validator Changes -* no changes +* Snapshot Generation Changes: +** Check for slicenames without any slicing +** Check for additional slicing rules in a set of slices +** Check that the minimum cardinality of a set of slices is correct +* Clean up duplicate errors when dates/dateTimes/instants have invalid formats +* Handle unknown code systems consistently when validating coded elements ## Other code changes -* no changes \ No newline at end of file +* Add support for R4B to loader diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java index eabb1659b..91af7f495 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java @@ -75,6 +75,8 @@ public abstract class BaseLoaderR5 implements IContextResourceLoader { protected BaseLoaderR5 loaderFactory(NpmPackage npm) throws JsonSyntaxException, IOException { if (VersionUtilities.isR5Plus(npm.fhirVersion())) { return new R5ToR5Loader(types, lkp.forNewPackage(npm)); + } else if (VersionUtilities.isR4BVer(npm.fhirVersion())) { + return new R4BToR5Loader(types, lkp.forNewPackage(npm), npm.version()); } else if (VersionUtilities.isR4Ver(npm.fhirVersion())) { return new R4ToR5Loader(types, lkp.forNewPackage(npm), npm.version()); } else if (VersionUtilities.isR3Ver(npm.fhirVersion())) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java index e5d5ca58b..dfe5d9f8f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java @@ -8,14 +8,20 @@ import java.util.Set; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.conformance.ElementRedirection; +import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; +import org.hl7.fhir.r5.model.OperationOutcome.IssueType; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationMessage.Source; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -267,12 +273,28 @@ public class ProfilePathProcessor { if (!diffMatches.get(0).hasSlicing()) outcome.setSlicing(profileUtilities.makeExtensionSlicing()); - else + else { outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); + for (int i = 1; i < diffMatches.size(); i++) { + if (diffMatches.get(i).hasSlicing()) { + if (!slicingMatches(diffMatches.get(0).getSlicing(), diffMatches.get(i).getSlicing())) { + profileUtilities.getMessages().add(new ValidationMessage(Source.InstanceValidator, ValidationMessage.IssueType.BUSINESSRULE, diffMatches.get(0).getPath(), + profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_CHANGE_SLICING, diffMatches.get(0).getId(), slicingSummary(diffMatches.get(0).getSlicing()), diffMatches.get(i).getId(), slicingSummary(diffMatches.get(i).getSlicing())), + ValidationMessage.IssueSeverity.ERROR)); + } else { + profileUtilities.getMessages().add(new ValidationMessage(Source.InstanceValidator, ValidationMessage.IssueType.BUSINESSRULE, diffMatches.get(0).getPath(), + profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_CHANGE_SLICING, diffMatches.get(0).getId(), diffMatches.get(i).getId()), + IssueSeverity.INFORMATION)); + + } + } + } + } if (cursors.resultPathBase != null) { if (!outcome.getPath().startsWith(cursors.resultPathBase)) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); } + debugCheck(outcome); getResult().getElement().add(outcome); slicerElement = outcome; @@ -337,6 +359,20 @@ public class ProfilePathProcessor { cursors.diffCursor = newDiffLimit + 1; } + private String slicingSummary(ElementDefinitionSlicingComponent s) { + return s.toString(); + } + + private boolean slicingMatches(ElementDefinitionSlicingComponent s1, ElementDefinitionSlicingComponent s2) { + if ((!s1.hasOrdered() && s2.hasOrdered()) || (s1.hasOrdered() && s2.hasOrdered() && !Base.compareDeep(s1.getOrderedElement(), s2.getOrderedElement(), false))) { + return false; + } + if ((!s1.hasRules() && s2.hasRules()) || (s1.hasRules() && s2.hasRules() && !Base.compareDeep(s1.getRulesElement(), s2.getRulesElement(), false))) { + return false; + } + return Base.compareDeep(s1.getDiscriminator(), s2.getDiscriminator(), false); + } + private void processSimplePathWhereDiffsConstrainTypes(String currentBasePath, List diffMatches, List typeList, ProfilePathProcessorState cursors) { int start = 0; int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); @@ -344,13 +380,14 @@ public class ProfilePathProcessor { ElementDefinition elementToRemove = null; boolean shortCut = !typeList.isEmpty() && typeList.get(0).getType() != null; // we come here whether they are sliced in the diff, or whether the short cut is used. + String path = diffMatches.get(0).getPath(); if (shortCut) { // this is the short cut method, we've just dived in and specified a type slice. // in R3 (and unpatched R4, as a workaround right now... if (!VersionUtilities.isR4Plus(profileUtilities.getContext().getVersion()) || !profileUtilities.isNewSlicingProcessing()) { // newSlicingProcessing is a work around for editorial loop dependency // we insert a cloned element with the right types at the start of the diffMatches ElementDefinition ed = new ElementDefinition(); - ed.setPath(profileUtilities.determineTypeSlicePath(diffMatches.get(0).getPath(), currentBasePath)); + ed.setPath(profileUtilities.determineTypeSlicePath(path, currentBasePath)); for (TypeSlice ts : typeList) ed.addType().setCode(ts.getType()); ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); @@ -365,7 +402,7 @@ public class ProfilePathProcessor { // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element ElementDefinition ed = new ElementDefinition(); - ed.setPath(profileUtilities.determineTypeSlicePath(diffMatches.get(0).getPath(), currentBasePath)); + ed.setPath(profileUtilities.determineTypeSlicePath(path, currentBasePath)); ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); @@ -374,6 +411,13 @@ public class ProfilePathProcessor { getDifferential().getElement().add(newDiffCursor, ed); elementToRemove = ed; } + } else { // if it's not a short cut, then the path has to be correct + String t1 = currentBasePath.substring(currentBasePath.lastIndexOf(".")+1); + String t2 = path.substring(path.lastIndexOf(".")+1); + if (!t1.equals(t2)) { + throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ED_PATH_WRONG_TYPE_MATCH, path.replace(t2, t1), path)); + } + } int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); // the first element is setting up the slicing @@ -429,7 +473,7 @@ public class ProfilePathProcessor { .processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, cursors.contextName, cursors.resultPathBase)); if (elementDefinition == null) - throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath())); + throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, path)); // now set up slicing on the e (cause it was wiped by what we called. elementDefinition.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); elementDefinition.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); @@ -607,6 +651,7 @@ public class ProfilePathProcessor { cursors.resultPathBase = outcome.getPath(); else if (!outcome.getPath().startsWith(cursors.resultPathBase)) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); + debugCheck(outcome); getResult().getElement().add(outcome); cursors.baseCursor++; cursors.diffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)) + 1; @@ -731,6 +776,7 @@ public class ProfilePathProcessor { cursors.resultPathBase = outcome.getPath(); else if (!outcome.getPath().startsWith(cursors.resultPathBase)) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__, outcome.getPath(), cursors.resultPathBase)); + debugCheck(outcome); getResult().getElement().add(outcome); if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), true)) { // well, the profile walks into this, so we need to as well @@ -756,6 +802,9 @@ public class ProfilePathProcessor { } } } + if (!profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) { + cursors.diffCursor++; + } int start = cursors.diffCursor; while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) cursors.diffCursor++; @@ -807,7 +856,7 @@ public class ProfilePathProcessor { } cursors.contextName = dt.getUrl(); if (getRedirector() == null || getRedirector().isEmpty()) { - + this .incrementDebugIndent() .withBaseLimit(dt.getSnapshot().getElement().size() - 1) @@ -893,6 +942,7 @@ public class ProfilePathProcessor { diffMatches.get(0).setUserData(profileUtilities.UD_GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called } + debugCheck(outcome); getResult().getElement().add(outcome); if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice @@ -942,6 +992,8 @@ public class ProfilePathProcessor { int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); for (int i = cursors.baseCursor + 1; i <= newBaseLimit; i++) { outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(i).copy()); + outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); + debugCheck(outcome); getResult().getElement().add(outcome); } } @@ -978,6 +1030,7 @@ public class ProfilePathProcessor { cursors.diffCursor = newDiffLimit + 1; diffpos++; } else { + debugCheck(outcome); getResult().getElement().add(outcome); cursors.baseCursor++; // just copy any children on the base @@ -988,6 +1041,7 @@ public class ProfilePathProcessor { throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); outcome.setUserData(profileUtilities.UD_BASE_PATH, outcome.getPath()); outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl()); + debugCheck(outcome); getResult().getElement().add(outcome); cursors.baseCursor++; } @@ -1016,6 +1070,7 @@ public class ProfilePathProcessor { outcome.setMin(0); // we're in a slice, so it's only a mandatory if it's explicitly marked so if (!outcome.getPath().startsWith(cursors.resultPathBase)) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); + debugCheck(outcome); getResult().getElement().add(outcome); profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived()); profileUtilities.removeStatusExtensions(outcome); @@ -1079,6 +1134,12 @@ public class ProfilePathProcessor { cursors.baseCursor++; } + private void debugCheck(ElementDefinition outcome) { + if (outcome.getPath().startsWith("List.") && "http://nictiz.nl/fhir/StructureDefinition/Bundle-MedicationOverview".equals(url)) { + System.out.println("wrong!"); + } + } + private void processPathWithSlicedBaseWhereDiffsConstrainTypes(String currentBasePath, List diffMatches, List typeList, ProfilePathProcessorState cursors) { int start = 0; int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); @@ -1252,6 +1313,7 @@ public class ProfilePathProcessor { cursors.resultPathBase = outcome.getPath(); else if (!outcome.getPath().startsWith(cursors.resultPathBase)) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); + debugCheck(outcome); getResult().getElement().add(outcome); // the profile walks into this, so we need to as well // did we implicitly step into a new type? @@ -1295,6 +1357,7 @@ public class ProfilePathProcessor { outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); if (!outcome.getPath().startsWith(cursors.resultPathBase)) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, getProfileName(), outcome.getPath(), cursors.resultPathBase)); + debugCheck(outcome); getResult().getElement().add(outcome); // so we just copy it in outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl()); outcome.setUserData(profileUtilities.UD_BASE_PATH, cursors.resultPathBase); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index b3e384870..2279c688b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -49,6 +49,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.conformance.ElementRedirection; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.AllowUnknownProfile; +import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementDefinitionCounter; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.ObjectConverter; import org.hl7.fhir.r5.elementmodel.Property; @@ -56,6 +57,7 @@ import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.DataType; +import org.hl7.fhir.r5.model.Element; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent; @@ -130,6 +132,33 @@ import org.hl7.fhir.utilities.xml.SchematronWriter.Section; */ public class ProfileUtilities extends TranslatingUtilities { + public class ElementDefinitionCounter { + int count = 0; + ElementDefinition focus; + + public ElementDefinitionCounter(ElementDefinition ed) { + focus = ed; + } + + public int update() { + if (count > focus.getMin()) { + int was = focus.getMin(); + focus.setMin(count); + return was; + } + return -1; + } + + public void count(ElementDefinition ed) { + count = count + ed.getMin(); + } + + public ElementDefinition getFocus() { + return focus; + } + + } + public enum MappingMergeModeOption { DUPLICATE, // if there's more than one mapping for the same URI, just keep them all IGNORE, // if there's more than one, keep the first @@ -288,6 +317,7 @@ public class ProfileUtilities extends TranslatingUtilities { private Map childMapCache = new HashMap<>(); private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES; private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND; + private boolean forPublication; public ProfileUtilities(IWorkerContext context, List messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { super(); @@ -690,6 +720,49 @@ public class ProfileUtilities extends TranslatingUtilities { } } } + // check slicing is ok while we're at it. and while we're doing this. update the minimum count if we need to + String tn = derived.getType(); + if (tn.contains("/")) { + tn = tn.substring(tn.lastIndexOf("/")+1); + } + System.out.println("Check slicing for "+derived.getVersionedUrl()); + Map slices = new HashMap<>(); + int i = 0; + for (ElementDefinition ed : derived.getSnapshot().getElement()) { + if (ed.hasSlicing()) { + slices.put(ed.getPath(), new ElementDefinitionCounter(ed)); + System.out.println("Entering slicing for "+ed.getPath()+" ["+i+"]"); + } else { + Set toRemove = new HashSet<>(); + for (String s : slices.keySet()) { + if (Utilities.charCount(s, '.') >= Utilities.charCount(ed.getPath(), '.') && !s.equals(ed.getPath())) { + toRemove.add(s); + } + } + for (String s : toRemove) { + int was = slices.get(s).update(); + if (was > -1) { + String msg = "The slice definition for "+slices.get(s).getFocus().getId()+" had a minimum of "+was+" but the slices added up to a minimum of "+slices.get(s).getFocus().getMin()+" so the value has been adjusted in the snapshot"; + System.out.println(msg); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION)); + } + System.out.println("Exiting slicing for "+s+" at "+ed.getPath()+" ["+i+"]"); + slices.remove(s); + } + } + if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) { + throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" (["+i+"]) doesn't have the right path (should start with "+tn+"."); + } + if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) { + String msg = "The element "+ed.getId()+" (["+i+"]) launches straight into slicing without the slicing being set up properly first"; + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); + } + if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { + slices.get(ed.getPath()).count(ed); + } + i++; + } + // last, check for wrong profiles or target profiles for (ElementDefinition ed : derived.getSnapshot().getElement()) { for (TypeRefComponent t : ed.getType()) { @@ -3978,5 +4051,16 @@ public class ProfileUtilities extends TranslatingUtilities { return defn.getIsModifier(); } - + public boolean isForPublication() { + return forPublication; + } + + public void setForPublication(boolean forPublication) { + this.forPublication = forPublication; + } + + public List getMessages() { + return messages; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 6556f4d22..fdc11fd65 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -943,6 +943,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (!t.hasResult()) { try { ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs); + vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null); ValidationResult res = vsc.validateCode("Coding", t.getCoding()); if (txCache != null) { txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT); @@ -1059,12 +1060,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } List issues = new ArrayList<>(); + Set unknownSystems = new HashSet<>(); String localError = null; if (options.isUseClient()) { // ok, first we try to validate locally try { ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs, ctxt); + vsc.setUnknownSystems(unknownSystems); + vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null); if (!ValueSetUtilities.isServerSide(code.getSystem())) { res = vsc.validateCode(path, code); if (txCache != null) { @@ -1084,7 +1088,11 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } if (localError != null && tcc.getClient() == null) { - return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.UNKNOWN, issues); + if (unknownSystems.size() > 0) { + return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues).setUnknownSystems(unknownSystems); + } else { + return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.UNKNOWN, issues); + } } if (!options.isUseServer()) { return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localError), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues); @@ -1204,11 +1212,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte codeSystemsUsed.add(c.getSystem()); } } + Set unknownSystems = new HashSet<>(); if (options.isUseClient()) { // ok, first we try to validate locally try { ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs); + vsc.setUnknownSystems(unknownSystems); + vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null); res = vsc.validateCode("CodeableConcept", code); txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); return res; @@ -1349,6 +1360,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte version = ((PrimitiveType) p.getValue()).asStringValue(); } else if (p.getName().equals("code")) { code = ((PrimitiveType) p.getValue()).asStringValue(); + } else if (p.getName().equals("x-caused-by-unknown-system")) { + err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED; } else if (p.getName().equals("cause")) { try { IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 4c084f5ac..9b90702ea 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -123,6 +123,7 @@ public interface IWorkerContext { private String diagnostics; private List issues = new ArrayList<>(); private CodeableConcept codeableConcept; + private Set unknownSystems; @Override public String toString() { @@ -239,7 +240,12 @@ public interface IWorkerContext { this.message = message; return this; } - + + public ValidationResult addToMessage(String message) { + this.message = this.message == null ? message : this.message +"; "+ message; + return this; + } + public ValidationResult setErrorClass(TerminologyServiceErrorClass errorClass) { this.errorClass = errorClass; return this; @@ -288,7 +294,16 @@ public interface IWorkerContext { public CodeableConcept getCodeableConcept() { return codeableConcept; } - + + public Set getUnknownSystems() { + return unknownSystems; + } + + public ValidationResult setUnknownSystems(Set unknownSystems) { + this.unknownSystems = unknownSystems; + return this; + } + } public class CodingValidationRequest { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java index 2750242f4..cba4d9905 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java @@ -113,6 +113,9 @@ public class Manager { } public static ParserBase makeParser(IWorkerContext context, FhirFormat format) { + if (format == null) { + throw new Error("Programming logic error: no format known"); + } switch (format) { case JSON : return new JsonParser(context); case XML : return new XmlParser(context); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java index 2ecd5e6d5..a9dc7ec36 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java @@ -1413,12 +1413,17 @@ public class ElementDefinition extends BackboneType implements ICompositeType { , ordered, rules); } - public String fhirType() { - return "ElementDefinition.slicing"; + public String fhirType() { + return "ElementDefinition.slicing"; - } + } - } + @Override + public String toString() { + return (ordered == null ? "??" : "true".equals(ordered.asStringValue()) ? "ordered" : "unordered")+"/"+ + (rules == null ? "??" : rules.asStringValue())+" "+discriminator.toString(); + } + } @Block() public static class ElementDefinitionSlicingDiscriminatorComponent extends Element implements IBaseDatatypeElement { @@ -1671,6 +1676,11 @@ public class ElementDefinition extends BackboneType implements ICompositeType { } + @Override + public String toString() { + return (type == null ? "??" : type.getCode()) + "="+(path == null ? "??" : path.asStringValue()); + } + } @Block() diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index 909819493..568344a5f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -167,7 +167,7 @@ public class ElementWrappers { Property family = b.getChildByName("family"); Property given = wrapped.getChildByName("given"); String s = given != null && given.hasValues() ? given.getValues().get(0).primitiveValue() : ""; - if (family != null && family.hasValues()) + if (family != null && family.hasValues() && family.getValues().get(0).primitiveValue() != null) s = s + " " + family.getValues().get(0).primitiveValue().toUpperCase(); return s; } else { 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 84d406112..a59e73127 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 @@ -11,6 +11,13 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; public class ValidationProcessInfo { private TerminologyServiceErrorClass err; private List issues = new ArrayList<>(); + + public ValidationProcessInfo() { + } + + public ValidationProcessInfo(List issues) { + this.issues = issues; + } public TerminologyServiceErrorClass getErr() { return err; } 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 99e738c9a..178ae1bf1 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 @@ -100,6 +100,8 @@ public class ValueSetValidator { private List localSystems = new ArrayList<>(); Parameters expansionProfile; private TerminologyCapabilities txCaps; + private Set unknownSystems; + private boolean throwToServer; public ValueSetValidator(ValidationOptions options, ValueSet source, IWorkerContext context, Parameters expansionProfile, TerminologyCapabilities txCaps) { this.valueset = source; @@ -120,6 +122,22 @@ public class ValueSetValidator { analyseValueSet(); } + public Set getUnknownSystems() { + return unknownSystems; + } + + public void setUnknownSystems(Set unknownSystems) { + this.unknownSystems = unknownSystems; + } + + public boolean isThrowToServer() { + return throwToServer; + } + + public void setThrowToServer(boolean throwToServer) { + this.throwToServer = throwToServer; + } + private void analyseValueSet() { if (localContext != null) { if (valueset != null) { @@ -174,10 +192,16 @@ public class ValueSetValidator { if (context.isNoTerminologyServer()) { if (c.hasVersion()) { String msg = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM_VERSION, c.getSystem(), c.getVersion() , resolveCodeSystemVersions(c.getSystem()).toString()); - res = new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg)); + if (valueSetDependsOn(c.getSystem(), c.getVersion())) { + unknownSystems.add(c.getSystem()+"|"+c.getVersion()); + } + res = new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg)).setUnknownSystems(unknownSystems); } else { String msg = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, c.getSystem(), c.getVersion()); - res = new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg)); + if (valueSetDependsOn(c.getSystem(), null)) { + unknownSystems.add(c.getSystem()); + } + res = new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg)).setUnknownSystems(unknownSystems); } } else { res = context.validateCode(options.withNoClient(), c, null); @@ -191,8 +215,9 @@ public class ValueSetValidator { } } Coding foundCoding = null; + String msg = null; + Boolean result = false; if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { - Boolean result = false; CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", "); for (Coding c : code.getCoding()) { @@ -212,11 +237,11 @@ public class ValueSetValidator { } } if (result == null) { - String msg = context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), b.toString()); - info.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path, msg)); + msg = context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), b.toString()); + info.getIssues().addAll(makeIssue(IssueSeverity.WARNING, unknownSystems.isEmpty() ? IssueType.INVALID : IssueType.NOTFOUND, path, msg)); } else if (!result) { - String msg = context.formatMessagePlural(code.getCoding().size(), I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), b.toString()); - info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, msg)); + msg = context.formatMessagePlural(code.getCoding().size(), I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), b.toString()); + info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, unknownSystems.isEmpty() ? IssueType.INVALID : IssueType.NOTFOUND, path, msg)); } } if (info.hasErrors()) { @@ -229,8 +254,11 @@ public class ValueSetValidator { res.setVersion(foundCoding.hasVersion() ? foundCoding.getVersion() : ((CodeSystem) foundCoding.getUserData("cs")).getVersion()); res.setDisplay(cd.getDisplay()); } + res.setUnknownSystems(unknownSystems); res.addCodeableConcept(vcc); return res; + } else if (result == null) { + return new ValidationResult(IssueSeverity.WARNING, info.summary(), info.getIssues()); } else if (foundCoding == null) { return new ValidationResult(IssueSeverity.ERROR, "Internal Error that should not happen", makeIssue(IssueSeverity.FATAL, IssueType.EXCEPTION, path, "Internal Error that should not happen")); } else if (info.getIssues().size() > 0) { @@ -245,6 +273,15 @@ public class ValueSetValidator { } } + private boolean valueSetDependsOn(String system, String version) { + for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { + if (system.equals(inc.getSystem()) && (version == null || inc.getVersion() == null || version.equals(inc.getVersion()))) { + return true; + } + } + return false; + } + private String getVersion(Coding c) { if (c.hasVersion()) { return c.getVersion(); @@ -329,6 +366,7 @@ public class ValueSetValidator { ValidationResult res = null; boolean inExpansion = false; boolean inInclude = false; + List issues = new ArrayList<>(); VersionInfo vi = new VersionInfo(this); String system = code.hasSystem() ? code.getSystem() : getValueSetSystemOrNull(); @@ -363,8 +401,10 @@ public class ValueSetValidator { if (cs == null) { if (wv == null) { warningMessage = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, system); + unknownSystems.add(system); } else { warningMessage = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM_VERSION, system, wv, resolveCodeSystemVersions(system).toString()); + unknownSystems.add(system+"|"+wv); } if (!inExpansion) { if (valueset != null && valueset.hasExpansion()) { @@ -372,18 +412,18 @@ public class ValueSetValidator { valueset.getUrl(), code.getSystem(), code.getCode().toString()); - List issues = new ArrayList<>(); issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path, msg)); throw new VSCheckerException(msg, issues); } else { - List issues = new ArrayList<>(); issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".system", warningMessage)); + res = new ValidationResult(IssueSeverity.WARNING, warningMessage, issues); if (valueset == null) { throw new VSCheckerException(warningMessage, issues); } else { - String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), code.toString()); - issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, msg)); - throw new VSCheckerException(warningMessage+"; "+msg, issues); +// String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), code.toString()); +// issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, msg)); + // we don't do this yet + // throw new VSCheckerException(warningMessage, issues); } } } @@ -407,7 +447,7 @@ public class ValueSetValidator { // we'll take it on faith String disp = getPreferredDisplay(cc); res = new ValidationResult(system, cs.getVersion(), new ConceptDefinitionComponent().setCode(cc.getCode()).setDisplay(disp), disp); - res.setMessage("Resolved system "+system+", but the definition is not complete, so assuming value set include is correct"); + res.addToMessage("Resolved system "+system+", but the definition is not complete, so assuming value set include is correct"); return res; } } @@ -419,12 +459,14 @@ public class ValueSetValidator { // we just take the value set as face value then res = new ValidationResult(system, wv, new ConceptDefinitionComponent().setCode(code.getCode()).setDisplay(code.getDisplay()), code.getDisplay()); if (!preferServerSide(system)) { - res.setMessage("Code System unknown, so assuming value set expansion is correct ("+warningMessage+")"); + res.addToMessage("Code System unknown, so assuming value set expansion is correct ("+warningMessage+")"); } } else { // well, we didn't find a code system - try the expansion? // disabled waiting for discussion - throw new FHIRException("No try the server"); + if (throwToServer) { + throw new FHIRException("No; try the server"); + } } } else { inExpansion = checkExpansion(code, vi); @@ -432,7 +474,7 @@ public class ValueSetValidator { } String wv = vi.getVersion(system, code.getVersion()); - ValidationProcessInfo info = new ValidationProcessInfo(); + ValidationProcessInfo info = new ValidationProcessInfo(issues); // then, if we have a value set, we check it's in the value set if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { @@ -446,20 +488,24 @@ public class ValueSetValidator { res.setErrorClass(info.getErr()); } if (ok == null) { - res.setMessage("Unable to check whether code is in value set "+valueset.getUrl()+": "+info.summary()).setSeverity(IssueSeverity.WARNING); - res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.EXCEPTION, path, res.getMessage())); + String m = "Unable to check whether the code is in the value set "+valueset.getVersionedUrl(); + res.addToMessage(m); + res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.NOTFOUND, path, m)); + res.setUnknownSystems(unknownSystems); + res.setSeverity(IssueSeverity.ERROR); // back patching for display logic issue } else if (!inExpansion && !inInclude) { - if (!info.getIssues().isEmpty()) { - res.setMessage("Not in value set "+valueset.getUrl()+": "+info.summary()).setSeverity(IssueSeverity.ERROR); - res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, res.getMessage())); - } else { - String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), code.toString()); - res.setMessage(msg).setSeverity(IssueSeverity.ERROR); +// if (!info.getIssues().isEmpty()) { +// res.setMessage("Not in value set "+valueset.getUrl()+": "+info.summary()).setSeverity(IssueSeverity.ERROR); +// res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, res.getMessage())); +// } else +// { + String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), code.toString()); + res.addToMessage(msg).setSeverity(IssueSeverity.ERROR); res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, msg)); res.setDefinition(null); res.setSystem(null); res.setDisplay(null); - } +// } } else if (warningMessage!=null) { String msg = context.formatMessage(I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_, warningMessage); res = new ValidationResult(IssueSeverity.WARNING, msg, makeIssue(IssueSeverity.WARNING, IssueType.EXCEPTION, path, msg)); @@ -472,7 +518,7 @@ public class ValueSetValidator { } } } else if ((res != null && !res.isOk())) { - String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), code.toString()); + String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), code.toString()); res.setMessage(res.getMessage()+"; "+msg); res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, msg)); } @@ -483,7 +529,7 @@ public class ValueSetValidator { return res; } - private static final HashSet SERVER_SIDE_LIST = new HashSet<>(Arrays.asList("http://fdasis.nlm.nih.gov", "http://hl7.org/fhir/sid/ndc", "http://loinc.org", "http://snomed.info/sct", "http://unitsofmeasure.org", + private static final Set SERVER_SIDE_LIST = new HashSet<>(Arrays.asList("http://fdasis.nlm.nih.gov", "http://hl7.org/fhir/sid/ndc", "http://loinc.org", "http://snomed.info/sct", "http://unitsofmeasure.org", "http://unstats.un.org/unsd/methods/m49/m49.htm", "http://varnomen.hgvs.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "https://www.usps.com/", "urn:ietf:bcp:13","urn:ietf:bcp:47","urn:ietf:rfc:3986", "urn:iso:std:iso:3166","urn:iso:std:iso:4217", "urn:oid:1.2.36.1.2001.1005.17")); @@ -917,6 +963,7 @@ public class ValueSetValidator { public Boolean codeInValueSet(String system, String version, String code, ValidationProcessInfo info) throws FHIRException { return codeInValueSet("code", system, version, code, info); } + public Boolean codeInValueSet(String path, String system, String version, String code, ValidationProcessInfo info) throws FHIRException { if (valueset == null) { return false; @@ -991,30 +1038,42 @@ public class ValueSetValidator { // ok, we need the code system CodeSystem cs = resolveCodeSystem(system, version); if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)) { - // make up a transient value set with - ValueSet vs = new ValueSet(); - vs.setStatus(PublicationStatus.ACTIVE); - vs.setUrl(valueset.getUrl()+"--"+vsiIndex); - vs.setVersion(valueset.getVersion()); - vs.getCompose().addInclude(vsi); - ValidationResult res = context.validateCode(options.withNoClient(), new Coding(system, code, null), vs); - if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { - if (info != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { - // server didn't know the code system either - we'll take it face value - info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.UNKNOWN, path, context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system))); - for (ConceptReferenceComponent cc : vsi.getConcept()) { - if (cc.getCode().equals(code)) { - return true; + if (throwToServer) { + // make up a transient value set with + ValueSet vs = new ValueSet(); + vs.setStatus(PublicationStatus.ACTIVE); + vs.setUrl(valueset.getUrl()+"--"+vsiIndex); + vs.setVersion(valueset.getVersion()); + vs.getCompose().addInclude(vsi); + ValidationResult res = context.validateCode(options.withNoClient(), new Coding(system, code, null), vs); + if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { + if (info != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { + // server didn't know the code system either - we'll take it face value + info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.UNKNOWN, path, context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system))); + for (ConceptReferenceComponent cc : vsi.getConcept()) { + if (cc.getCode().equals(code)) { + return true; + } } + info.setErr(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); + return null; } - info.setErr(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); + return false; } - return false; + if (res.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) { + throw new NoTerminologyServiceException(); + } + return res.isOk(); + } else { + if (unknownSystems != null) { + if (version == null) { + unknownSystems.add(system); + } else { + unknownSystems.add(system+"|"+version); + } + } + return null; } - if (res.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) { - throw new NoTerminologyServiceException(); - } - return res.isOk(); } else { if (vsi.hasFilter()) { ok = true; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java index f36db48be..7f26bb8f9 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java @@ -100,7 +100,7 @@ public interface IResourceValidator { void setNoUnicodeBiDiControlChars(boolean noUnicodeBiDiControlChars); boolean isForPublication(); - void setForPublication(boolean forPublication); + IResourceValidator setForPublication(boolean forPublication); /** * Whether being unable to resolve a profile in found in Resource.meta.profile or ElementDefinition.type.profile or targetProfile is an error or just a warning diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java index 4b551d4c5..7429723a6 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java @@ -585,8 +585,8 @@ public class SnapShotGenerationTests { boolean structureDefinitionEquality = t1.equalsDeep(t2); if (!structureDefinitionEquality) { - System.out.println("Encountered unexpected diff in structure definition"); - DiffUtils.testDiff(dst.getAbsolutePath(), actualFilePath); + System.out.println("Encountered unexpected change in diff in structure definition"); +// DiffUtils.testDiff(dst.getAbsolutePath(), actualFilePath); } Assertions.assertTrue(structureDefinitionEquality, "Output does not match expected"); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index e6b735e45..47f3f7b6d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -590,7 +590,7 @@ public class Utilities { public static boolean isPlural(String word) { word = word.toLowerCase(); if ("restricts".equals(word) || "contains".equals(word) || "data".equals(word) || "specimen".equals(word) || "replaces".equals(word) || "addresses".equals(word) - || "supplementalData".equals(word) || "instantiates".equals(word) || "imports".equals(word)) + || "supplementalData".equals(word) || "instantiates".equals(word) || "imports".equals(word) || "covers".equals(word)) return false; Inflector inf = new Inflector(); return !inf.singularize(word).equals(word); @@ -1966,4 +1966,8 @@ public class Utilities { return Utilities.startsWithInList(s.replace("https://", "http://"), FhirSettings.getTxFhirProduction(), FhirSettings.getTxFhirDevelopment(), FhirSettings.getTxFhirLocal()); } + public static String[] splitLines(String txt) { + return txt.split("\\r?\\n|\\r"); + } + } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 1c3d65423..5b5dcf412 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -866,6 +866,9 @@ public class I18nConstants { public static final String SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = "SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION"; public static final String SM_TARGET_TRANSFORM_OP_UNKNOWN_SOURCE = "SM_TARGET_TRANSFORM_OP_UNKNOWN_SOURCE"; public static final String SM_TARGET_TRANSFORM_OP_INVALID_TYPE = "SM_TARGET_TRANSFORM_OP_INVALID_TYPE"; + public static final String ED_PATH_WRONG_TYPE_MATCH = "ED_PATH_WRONG_TYPE_MATCH"; + public static final String ATTEMPT_TO_CHANGE_SLICING = "ATTEMPT_TO_CHANGE_SLICING"; + public static final String REPEAT_SLICING_IGNORED = "REPEAT_SLICING_IGNORED"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 462016ece..c7d8d4fa5 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -672,7 +672,7 @@ RENDER_BUNDLE_DOCUMENT_CONTENT = Additional Document Content RENDER_BUNDLE_HEADER_DOC_ENTRY_URD = {0}. {1} ({2}/{3}) RENDER_BUNDLE_HEADER_DOC_ENTRY_U = {0}. {1} RENDER_BUNDLE_HEADER_DOC_ENTRY_RD = {0}. {2}/{3} -UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ = Unable to determine whether the provided codes {1} are in the value set {0} because the value set or a code system it depends on is not known to the validator +UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ = Unable to check whether the code is in the value set {0} TERMINOLOGY_TX_SYSTEM_WRONG_HTML = The code system reference {0} is wrong - the code system reference cannot be to an HTML page. This may be the correct reference: {1} TERMINOLOGY_TX_SYSTEM_WRONG_BUILD = The code system reference {0} is wrong - the code system reference cannot be a reference to build.fhir.org. This may be the correct reference: {1} FHIRPATH_BAD_DATE = Unable to parse Date {0} @@ -920,6 +920,7 @@ NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the l SD_NO_CONTEXT_WHEN_NOT_EXTENSION = The type is {0} so an extension context should not be specified SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = The type is {0} so an extension context invariants should not be specified SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type: extensions should not have a context of {0} unless it''s really intended that they can be used anywhere - - +ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type list is not constrained +ATTEMPT_TO_CHANGE_SLICING = The element at {0} defines the slicing {1} but then an element in the slicing {2} tries to redefine the slicing to {3} +REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index 2d0be1a81..2c46c8566 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -156,6 +156,7 @@ public class BaseValidator implements IValidationContextResourceLoader { private ValidationLevel level = ValidationLevel.HINTS; protected Coding jurisdiction; protected boolean allowExamples; + protected boolean forPublication; public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager) { super(); @@ -1242,7 +1243,16 @@ public class BaseValidator implements IValidationContextResourceLoader { } protected boolean isExampleUrl(String url) { - return Utilities.containsInList(url, "example.org", "acme.com", "acme.org"); - + return Utilities.containsInList(url, "example.org", "acme.com", "acme.org"); } + + public boolean isForPublication() { + return forPublication; + } + + public BaseValidator setForPublication(boolean forPublication) { + this.forPublication = forPublication; + return this; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 1eb7997f9..093c6a696 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -505,7 +505,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private ValidationOptions baseOptions = new ValidationOptions(); private Map crLookups = new HashMap<>(); private boolean logProgress; - private boolean forPublication; public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) { super(theContext, xverManager); @@ -2394,18 +2393,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (type.equals("dateTime")) { - warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, - e.primitiveValue() - .matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, e.primitiveValue()) && ok; - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && ok; - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok; - try { - DateTimeType dt = new DateTimeType(e.primitiveValue()); - } catch (Exception ex) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage()); - ok = false; + boolean dok = ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, + e.primitiveValue() + .matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for dateTime") && ok; + dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && dok; + dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok; + if (dok) { + try { + DateTimeType dt = new DateTimeType(e.primitiveValue()); + warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); + } catch (Exception ex) { + rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage()); + dok = false; + } } + ok = ok && dok; } if (type.equals("time")) { ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, @@ -2419,15 +2421,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (type.equals("date")) { - warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID) && ok; - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok; - try { - DateType dt = new DateType(e.primitiveValue()); - } catch (Exception ex) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage()); - ok = false; + boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for date"); + dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok; + if (dok) { + try { + DateType dt = new DateType(e.primitiveValue()); + warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); + } catch (Exception ex) { + rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage()); + dok = false; + } } + ok = ok && dok; } if (type.equals("base64Binary")) { String encoded = e.primitiveValue(); @@ -2511,15 +2516,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (type.equals("instant")) { - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, - e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX, e.primitiveValue()) && ok; - warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); - try { - InstantType dt = new InstantType(e.primitiveValue()); - } catch (Exception ex) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage()); - ok = false; + boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, + e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX, "'"+e.primitiveValue()+"' doesn't meet format requirements for instant)"); + if (dok) { + try { + InstantType dt = new InstantType(e.primitiveValue()); + warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue()); + } catch (Exception ex) { + rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage()); + dok = false; + } } + ok = ok && dok; } if (type.equals("code") && e.primitiveValue() != null) { @@ -5040,7 +5048,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (element.getType().equals("SearchParameter")) { return new SearchParameterValidator(context, timeTracker, fpe, xverManager, jurisdiction).validateSearchParameter(errors, element, stack); } else if (element.getType().equals("StructureDefinition")) { - return new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager, jurisdiction).validateStructureDefinition(errors, element, stack); + return new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager, jurisdiction, forPublication).validateStructureDefinition(errors, element, stack); } else if (element.getType().equals("StructureMap")) { return new StructureMapValidator(context, timeTracker, fpe, xverManager,profileUtilities, jurisdiction).validateStructureMap(errors, element, stack); } else if (element.getType().equals("ValueSet")) { @@ -6527,14 +6535,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat this.logProgress = logProgress; } - public boolean isForPublication() { - return forPublication; - } - - public void setForPublication(boolean forPublication) { - this.forPublication = forPublication; - } - public boolean isDisplayWarnings() { return baseOptions.isDisplayWarningMode(); } @@ -6543,4 +6543,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat baseOptions.setDisplayWarningMode(displayWarnings); } + + public InstanceValidator setForPublication(boolean forPublication) { + this.forPublication = forPublication; + return this; + } } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index e7245bbcf..9cad656d9 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -60,13 +60,14 @@ public class StructureDefinitionValidator extends BaseValidator { private FHIRPathEngine fpe; private boolean wantCheckSnapshotUnchanged; - public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged, XVerExtensionManager xverManager, Coding jurisdiction) { + public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, boolean wantCheckSnapshotUnchanged, XVerExtensionManager xverManager, Coding jurisdiction, boolean forPublication) { super(context, xverManager); source = Source.InstanceValidator; this.fpe = fpe; this.timeTracker = timeTracker; this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; this.jurisdiction = jurisdiction; + this.forPublication = forPublication; } public boolean validateStructureDefinition(List errors, Element src, NodeStack stack) { @@ -89,6 +90,7 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && sd.getType().equals(base.getType()), I18nConstants.SD_CONSTRAINED_TYPE_NO_MATCH, sd.getType(), base.getType()); List msgs = new ArrayList<>(); ProfileUtilities pu = new ProfileUtilities(context, msgs, null); + pu.setForPublication(forPublication); pu.setXver(xverManager); pu.setNewSlicingProcessing(!sd.hasFhirVersion() || VersionUtilities.isR4Plus(sd.getFhirVersion().toCode())); pu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir/R4/", sd.getName()); @@ -121,7 +123,7 @@ public class StructureDefinitionValidator extends BaseValidator { I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")) && ok; } } - } catch (FHIRException | IOException e) { + } catch (Exception e) { rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); ok = false; } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java index b98ab090f..203d599c0 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java @@ -175,19 +175,19 @@ public class SnapShotGenerationXTests { } public void load(String version) throws FHIRFormatError, FileNotFoundException, IOException { - if (UtilitiesXTests.findTestResource("rX", "snapshot-generation", id + "-input.json")) - source = (StructureDefinition) XVersionLoader.loadJson(version, UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", id + "-input.json")); + if (TestingUtilities.findTestResource("rX", "snapshot-generation", id + "-input.json")) + source = (StructureDefinition) XVersionLoader.loadJson(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-input.json")); else - source = (StructureDefinition) XVersionLoader.loadXml(version, UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", id + "-input.xml")); + source = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-input.xml")); if (!fail) - expected = (StructureDefinition) XVersionLoader.loadXml(version, UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", id + "-expected.xml")); + expected = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-expected.xml")); if (!Utilities.noString(include)) - included = (StructureDefinition) XVersionLoader.loadXml(version, UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", include + ".xml")); + included = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", include + ".xml")); if (!Utilities.noString(register)) { - if (UtilitiesXTests.findTestResource("rX", "snapshot-generation", register + ".xml")) { - included = (StructureDefinition) XVersionLoader.loadXml(version, UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", register + ".xml")); + if (TestingUtilities.findTestResource("rX", "snapshot-generation", register + ".xml")) { + included = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", register + ".xml")); } else { - included = (StructureDefinition) XVersionLoader.loadJson(version, UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", register + ".json")); + included = (StructureDefinition) XVersionLoader.loadJson(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", register + ".json")); } } } @@ -404,7 +404,7 @@ public class SnapShotGenerationXTests { public static Iterable data() throws ParserConfigurationException, IOException, FHIRFormatError, SAXException { SnapShotGenerationTestsContext context = new SnapShotGenerationTestsContext(); - Document tests = XMLUtil.parseToDom(UtilitiesXTests.loadTestResource("rX", "snapshot-generation", "manifest.xml")); + Document tests = XMLUtil.parseToDom(TestingUtilities.loadTestResource("rX", "snapshot-generation", "manifest.xml")); Element test = XMLUtil.getFirstChild(tests.getDocumentElement()); List objects = new ArrayList(); while (test != null && test.getNodeName().equals("test")) { @@ -471,7 +471,7 @@ public class SnapShotGenerationXTests { pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors, false); if (!errors.isEmpty()) throw new FHIRException(errors.get(0)); - IOUtils.copy(UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml"))); + IOUtils.copy(TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml"))); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-actual.xml")), test.getOutput()); Assertions.assertTrue(test.expected.equalsDeep(test.output), "Output does not match expected"); } @@ -545,7 +545,7 @@ public class SnapShotGenerationXTests { File dst = new File(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml")); if (dst.exists()) dst.delete(); - IOUtils.copy(UtilitiesXTests.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(dst)); + IOUtils.copy(TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(dst)); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-actual.xml")), output); StructureDefinition t1 = test.expected.copy(); t1.setText(null); 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 dc2156dbd..6a7bb24ec 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 @@ -280,6 +280,11 @@ public class TerminologyServiceTests { 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 UriType(s)); + } + } if (vm.getIssues().size() > 0) { OperationOutcome oo = new OperationOutcome(); oo.getIssue().addAll(vm.getIssues()); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index 6fefd8b33..0bca11e59 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -202,6 +202,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe byte[] testCaseContent = TestingUtilities.loadTestResource("validator", JsonUtilities.str(content, "file")).getBytes(StandardCharsets.UTF_8); // load and process content FhirFormat fmt = determineFormat(content, testCaseContent); + if (fmt == null) { + throw new FHIRException("Unknown format in source "+JsonUtilities.str(content, "file")); + } InstanceValidator val = vCurr.getValidator(fmt); val.setWantCheckSnapshotUnchanged(true); @@ -238,6 +241,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe val.setValidationLanguage(content.get("language").getAsString()); else val.setValidationLanguage(null); + val.setForPublication(content.has("for-publication") && "true".equals(content.get("for-publication").getAsString())); if (content.has("default-version")) { val.setBaseOptions(val.getBaseOptions().withVersionFlexible(content.get("default-version").getAsBoolean())); } else { diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache index c4953b917..ff532930b 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache @@ -1116,3 +1116,39 @@ v: { "class" : "UNKNOWN" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "77176002", + "display" : "Smoker" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-bloodGroup", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Smoker", + "code" : "77176002", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "38341003", + "display" : "Hypertensive disorder, systemic arterial (disorder)" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-bloodGroup", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "High blood pressure", + "code" : "38341003", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/http___varnomen.hgvs.org.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/http___varnomen.hgvs.org.cache index 1d26e3c7f..213ff2f24 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/http___varnomen.hgvs.org.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/http___varnomen.hgvs.org.cache @@ -63,3 +63,35 @@ v: { "class" : "SERVER_ERROR" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://varnomen.hgvs.org", + "code" : "NC_000019.8:g.1171707G>A" +}, "url": "http://hl7.org/fhir/us/mcode/ValueSet/mcode-hgvs-vs", "version": "1.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "Error from server: Error parsing HGVS response: Error parsing JSON source: Unexpected char \"<\" in json stream at Line 0 (path=[])", + "class" : "SERVER_ERROR" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://varnomen.hgvs.org", + "code" : "NC_000019.8:g.1171707G>AXXX" +}, "url": "http://hl7.org/fhir/us/mcode/ValueSet/mcode-hgvs-vs", "version": "1.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "Error from server: Error parsing HGVS response: Error parsing JSON source: Unexpected char \"<\" in json stream at Line 0 (path=[])", + "class" : "SERVER_ERROR" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache index 476aab8d3..db0a89521 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache @@ -3167,3 +3167,279 @@ v: { "version" : "2.74" } ------------------------------------------------------------------------------------- +{"code" : { + "coding" : [{ + "system" : "http://loinc.org", + "code" : "59408-5", + "display" : "O2 % BldC Oximetry" + }, + { + "system" : "http://loinc.org", + "code" : "2708-6", + "display" : "Oxygen saturation in Arterial blood" + }] +}, "url": "http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs", "version": "4.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "Wrong Display Name 'O2 % BldC Oximetry' for http://loinc.org#59408-5 - should be one of 26 choices: 'Oxygen saturation in Arterial blood by Pulse oximetry, \"SaO2 % BldA PulseOx\", \"O2 SaO2\" (pl-PL), \"saturacja krwi tlenem\" (pl-PL), \"MFr O2\" (zh-CN), \"tO2\" (zh-CN), \"总氧\" (zh-CN), \"氧气 SaO2 动脉血 动脉血O2饱和度 可用数量表示的\" (zh-CN), \"定量性\" (zh-CN), \"数值型\" (zh-CN), \"数量型\" (zh-CN), \"连续数值型标尺 时刻\" (zh-CN), \"随机\" (zh-CN), \"随意\" (zh-CN), \"瞬间 肺部测量指标与呼吸机管理 脉搏血氧测定法\" (zh-CN), \"脉搏血氧定量\" (zh-CN), \"脉搏血氧测定\" (zh-CN), \"脉搏血氧仪 血氧测定法 饱和 饱和状态 饱和程度\" (zh-CN), \"O2-Sättigung\" (de-DE), \"Frazione di massa Gestione ventilazione polmonare Punto nel tempo (episodio) Sangue arterioso\" (it-IT), \"Oksijen doymuşluğu\" (tr-TR), \"Количественный Кровь артериальная Массовая доля Насыщение кислородом Оксигемометрия\" (ru-RU), \"Гемоксиметрия Точка во времени\" (ru-RU), \"Момент\" (ru-RU), \"zuurstofsaturatiemeting\" (nl-NL), \"O2 SatO2\" (fr-BE)' for the language(s) '--' (from Tx-Server)", + "class" : "UNKNOWN" +} +------------------------------------------------------------------------------------- +{"code" : { + "coding" : [{ + "system" : "http://loinc.org", + "code" : "3150-0", + "display" : "Inhaled Oxygen Concentration" + }] +}, "url": "http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs", "version": "4.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Inhaled oxygen concentration", + "code" : "3150-0", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "coding" : [{ + "system" : "http://loinc.org", + "code" : "3151-8", + "display" : "Flow Rate" + }] +}, "url": "http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs", "version": "4.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "Wrong Display Name 'Flow Rate' for http://loinc.org#3151-8 - should be one of 37 choices: 'Inhaled oxygen flow rate, \"Inhaled O2 flow rate\", \"O2\" (zh-CN), \"tO2\" (zh-CN), \"总氧\" (zh-CN), \"氧气 体积速率(单位时间)\" (zh-CN), \"单位时间内体积的变化速率\" (zh-CN), \"流量 可用数量表示的\" (zh-CN), \"定量性\" (zh-CN), \"数值型\" (zh-CN), \"数量型\" (zh-CN), \"连续数值型标尺 吸入气\" (zh-CN), \"吸入气体\" (zh-CN), \"吸入的空气 所吸入的氧\" (zh-CN), \"已吸入的氧气 时刻\" (zh-CN), \"随机\" (zh-CN), \"随意\" (zh-CN), \"瞬间 气 气体类 空气\" (zh-CN), \"Inhaled O2\" (pt-BR), \"vRate\" (pt-BR), \"Volume rate\" (pt-BR), \"Flow\" (pt-BR), \"Point in time\" (pt-BR), \"Random\" (pt-BR), \"IhG\" (pt-BR), \"Inhaled Gas\" (pt-BR), \"Inspired\" (pt-BR), \"Quantitative\" (pt-BR), \"QNT\" (pt-BR), \"Quant\" (pt-BR), \"Quan\" (pt-BR), \"Gases\" (pt-BR), \"Clinico Gas inalati Punto nel tempo (episodio) Tasso di Volume\" (it-IT), \"Количественный Объемная скорость Точка во времени\" (ru-RU), \"Момент\" (ru-RU), \"ingeademde O2\" (nl-NL), \"O2-Zufuhr\" (de-AT)' for the language(s) '--' (from Tx-Server)", + "class" : "UNKNOWN" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "883-9", + "display" : "ABO group [Type] in Blood" +}, "url": "http://hl7.org/fhir/ValueSet/observation-vitalsignresult", "version": "4.0.1", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "ABO group [Type] in Blood", + "code" : "883-9", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "883-9" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "ABO group [Type] in Blood", + "code" : "883-9", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "46418-0", + "display" : "INR in Capillary blood by Coagulation assay" +}, "url": "http://hl7.org/fhir/ValueSet/observation-vitalsignresult", "version": "4.0.1", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "INR in Capillary blood by Coagulation assay", + "code" : "46418-0", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "77140-2", + "display" : "Creatinine [Moles/volume] in Serum, Plasma or Blood" +}, "url": "http://hl7.org/fhir/ValueSet/observation-vitalsignresult", "version": "4.0.1", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Creatinine [Moles/volume] in Serum, Plasma or Blood", + "code" : "77140-2", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "4535-1", + "display" : "Cytotoxic percent reactive Ab [Presence] in Serum by Quick method" +}, "url": "http://hl7.org/fhir/ValueSet/observation-vitalsignresult", "version": "4.0.1", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Cytotoxic percent reactive Ab [Presence] in Serum by Quick method", + "code" : "4535-1", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "29463-7", + "display" : "Body weight" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-laboratory", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Body weight", + "code" : "29463-7", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "8302-2", + "display" : "Body height" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-laboratory", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Body height", + "code" : "8302-2", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "39156-5", + "display" : "Body mass index (BMI) [Ratio]" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-laboratory", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Body mass index (BMI) [Ratio]", + "code" : "39156-5", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "85354-9", + "display" : "Blood pressure panel with all children optional" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-laboratory", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Blood pressure panel with all children optional", + "code" : "85354-9", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "883-9", + "display" : "ABO group [Type] in Blood" +}, "url": "https://mednet.swiss/fhir/ValueSet/mni-obs-laboratory", "version": "0.5.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "ABO group [Type] in Blood", + "code" : "883-9", + "system" : "http://loinc.org", + "version" : "2.74" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "X-34133-9" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "Unknown Code 'X-34133-9' in the system 'http://loinc.org'; The provided code http://loinc.org#X-34133-9 is not in the value set 'http://hl7.org/fhir/ValueSet/@all' (from Tx-Server)", + "class" : "UNKNOWN" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "5792-7" +}, "valueSet" :{ + "resourceType" : "ValueSet" +}, "langs":"[]", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"true", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "severity" : "error", + "error" : "Error from server: Unable to resolve value set \"http://hl7.org/fhir/ValueSet/birthDate\"", + "class" : "SERVER_ERROR" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache index c4b75d6e7..b51a4f158 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache @@ -2116,3 +2116,37 @@ v: { "class" : "UNKNOWN" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "77176002" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Smoker", + "code" : "77176002", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "38341003" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "High blood pressure", + "code" : "38341003", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/dicom.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/dicom.cache new file mode 100644 index 000000000..71885930b --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/dicom.cache @@ -0,0 +1,19 @@ +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://dicom.nema.org/resources/ontology/DCM", + "code" : "110122", + "display" : "Login" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Login", + "code" : "110122", + "system" : "http://dicom.nema.org/resources/ontology/DCM", + "version" : "2023.1.20230123" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/snomed.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/snomed.cache index cec561d38..4996fd9cc 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/snomed.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/snomed.cache @@ -752,3 +752,73 @@ v: { "version" : "http://snomed.info/sct/900000000000207008/version/20230131" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "90655003", + "display" : "Geriatrics specialist" +}, "url": "http://hl7.org/fhir/ValueSet/c80-practice-codes", "version": "5.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"true", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Geriatrics specialist", + "code" : "90655003", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "90655003" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"true", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Geriatrics specialist", + "code" : "90655003", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "106004", + "display" : "Posterior carpal region" +}, "url": "http://hl7.org/fhir/ValueSet/clinical-findings", "version": "5.0.0", "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Posterior carpal region", + "code" : "106004", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "106004" +}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Posterior carpal region", + "code" : "106004", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230131" +} +------------------------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index d9af733e1..d185ac805 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 6.4.1 - 1.3.6 + 1.3.7-SNAPSHOT 2.14.0 5.9.2 1.8.2