Merge pull request #482 from hapifhir/gg-202104-bugfixes-2

Ensure that CVX uses tx.fhir.org, not UTG definitions which are wrong…
This commit is contained in:
Grahame Grieve 2021-04-22 13:59:45 +10:00 committed by GitHub
commit 8550fc37b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 212 additions and 5 deletions

View File

@ -0,0 +1,3 @@
Terminology: Ensure that CVX uses tx.fhir.org, not UTG definitions which are wrong
Validator: Fix problems with Bundle validation for ids in collections and add additional search related validation
Validator: Remove check on ElementDefinition.id for R2B

View File

@ -1,6 +1,6 @@
The Java Core Code Generator The Java Core Code Generator
Note: This code only generates tje R5 java code. Older generated models are now maintained by hand. Note: This code only generates the R5 java code. Older generated models are now maintained by hand.
To run this code, run the class JavaCoreGenerator with 3 parameters: To run this code, run the class JavaCoreGenerator with 3 parameters:
* 1: fhir version to generate from (e.g. 4.1.0 or 'current' * 1: fhir version to generate from (e.g. 4.1.0 or 'current'

View File

@ -63,7 +63,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode; import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode;
public class ValueSetCheckerSimple implements ValueSetChecker { public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChecker {
private ValueSet valueset; private ValueSet valueset;
private IWorkerContext context; private IWorkerContext context;

View File

@ -108,7 +108,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
public class ValueSetExpanderSimple implements ValueSetExpander { public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetExpander {
public class PropertyFilter implements IConceptFilter { public class PropertyFilter implements IConceptFilter {
@ -567,7 +567,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
copyImportContains(base.getExpansion().getContains(), null, expParams, imports); copyImportContains(base.getExpansion().getContains(), null, expParams, imports);
} else { } else {
CodeSystem cs = context.fetchCodeSystem(inc.getSystem()); CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
if ((cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { if (isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions); doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions);
} else { } else {
doInternalIncludeCodes(inc, exp, expParams, imports, cs); doInternalIncludeCodes(inc, exp, expParams, imports, cs);

View File

@ -0,0 +1,10 @@
package org.hl7.fhir.r5.terminologies;
import org.hl7.fhir.utilities.Utilities;
public class ValueSetWorker {
protected boolean isServerSide(String url) {
return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx");
}
}

View File

@ -631,6 +631,15 @@ public class I18nConstants {
public static final String BUNDLE_RULE_UNKNOWN = "BUNDLE_RULE_UNKNOWN"; public static final String BUNDLE_RULE_UNKNOWN = "BUNDLE_RULE_UNKNOWN";
public static final String BUNDLE_RULE_INVALID_INDEX = "BUNDLE_RULE_INVALID_INDEX"; public static final String BUNDLE_RULE_INVALID_INDEX = "BUNDLE_RULE_INVALID_INDEX";
public static final String BUNDLE_RULE_PROFILE_UNKNOWN = "BUNDLE_RULE_PROFILE_UNKNOWN"; public static final String BUNDLE_RULE_PROFILE_UNKNOWN = "BUNDLE_RULE_PROFILE_UNKNOWN";
public static final String BUNDLE_SEARCH_NOSELF = "BUNDLE_SEARCH_NOSELF";
public static final String BUNDLE_SEARCH_SELF_NOT_UNDERSTOOD = "BUNDLE_SEARCH_SELF_NOT_UNDERSTOOD";
public static final String BUNDLE_SEARCH_ENTRY_NO_RESOURCE = "BUNDLE_SEARCH_ENTRY_NO_RESOURCE";
public static final String BUNDLE_SEARCH_ENTRY_TYPE_NOT_SURE = "BUNDLE_SEARCH_ENTRY_TYPE_NOT_SURE";
public static final String BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID = "BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID";
public static final String BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_MODE = "BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_MODE";
public static final String BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_NO_MODE = "BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_NO_MODE";
public static final String BUNDLE_SEARCH_NO_MODE = "BUNDLE_SEARCH_NO_MODE";
public static final String BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_OUTCOME = "BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_OUTCOME";
} }

View File

@ -643,3 +643,12 @@ CODESYSTEM_CS_SUPP_INVALID_CODE = The code ''{1}'' is not declared in the base C
SD_VALUE_TYPE_IILEGAL = The element {0} has a {1} of type {2}, which is not in the list of allowed types ({3}) SD_VALUE_TYPE_IILEGAL = The element {0} has a {1} of type {2}, which is not in the list of allowed types ({3})
SD_NO_TYPES_OR_CONTENTREF = The element {0} has no assigned types, and no content reference SD_NO_TYPES_OR_CONTENTREF = The element {0} has no assigned types, and no content reference
CODESYSTEM_CS_UNK_EXPANSION = The code provided ({2}) is not in the value set {0}, and a code is required from this value set. The system {1} is unknown. CODESYSTEM_CS_UNK_EXPANSION = The code provided ({2}) is not in the value set {0}, and a code is required from this value set. The system {1} is unknown.
BUNDLE_SEARCH_NOSELF = SearchSet Bundles should have a self link that specifies what the search was
BUNDLE_SEARCH_SELF_NOT_UNDERSTOOD = No types could be determined from the search string, so the types can't be checked
BUNDLE_SEARCH_ENTRY_NO_RESOURCE = SearchSet Bundle Entries must have resources
BUNDLE_SEARCH_ENTRY_TYPE_NOT_SURE = Unable to determine if this resource is a valid resource type for this search
BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID = Search results must have ids
BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_MODE = This is not a matching resource type for the specified search ({0} expecting {1})
BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_OUTCOME = This is not an OperationOutcome ({0})
BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_NO_MODE = This is not a matching resource type for the specified search (is a search mode needed?) ({0} expecting {1})
BUNDLE_SEARCH_NO_MODE = SearchSet bundles should have search modes on the entries

View File

@ -125,9 +125,11 @@ public class BaseValidator {
protected final String META = "meta"; protected final String META = "meta";
protected final String ENTRY = "entry"; protected final String ENTRY = "entry";
protected final String LINK = "link";
protected final String DOCUMENT = "document"; protected final String DOCUMENT = "document";
protected final String RESOURCE = "resource"; protected final String RESOURCE = "resource";
protected final String MESSAGE = "message"; protected final String MESSAGE = "message";
protected final String SEARCHSET = "searchset";
protected final String ID = "id"; protected final String ID = "id";
protected final String FULL_URL = "fullUrl"; protected final String FULL_URL = "fullUrl";
protected final String PATH_ARG = ":0"; protected final String PATH_ARG = ":0";

View File

@ -2005,7 +2005,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
if (type.equals(ID)) { if (type.equals(ID)) {
// work around an old issue with ElementDefinition.id // work around an old issue with ElementDefinition.id
if (!context.getPath().equals("ElementDefinition.id") || VersionUtilities.versionsCompatible("1.4", this.context.getVersion())) { if (!context.getPath().equals("ElementDefinition.id")) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()); rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue());
} }
} }
@ -4166,6 +4166,24 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
hc = hostContext.forContained(element); hc = hostContext.forContained(element);
} }
stack.resetIds(); stack.resetIds();
if (element.getSpecial() != null) {
switch (element.getSpecial()) {
case BUNDLE_ENTRY:
idstatus = IdStatus.OPTIONAL;
break;
case BUNDLE_OUTCOME:
idstatus = IdStatus.OPTIONAL;
break;
case CONTAINED:
idstatus = IdStatus.REQUIRED;
break;
case PARAMETER:
idstatus = IdStatus.OPTIONAL;
break;
default:
break;
}
}
if (trr.getProfile().size() == 1) { if (trr.getProfile().size() == 1) {
long t = System.nanoTime(); long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, trr.getProfile().get(0).asStringValue()); StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, trr.getProfile().get(0).asStringValue());

View File

@ -73,6 +73,9 @@ public class BundleValidator extends BaseValidator{
} }
checkAllInterlinked(errors, entries, stack, bundle, VersionUtilities.isR5Ver(context.getVersion())); checkAllInterlinked(errors, entries, stack, bundle, VersionUtilities.isR5Ver(context.getVersion()));
} }
if (type.equals(SEARCHSET)) {
checkSearchSet(errors, bundle, entries, stack);
}
// We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles // We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles
// validateResourceIds(errors, entries, stack); // validateResourceIds(errors, entries, stack);
} }
@ -122,6 +125,159 @@ public class BundleValidator extends BaseValidator{
} }
} }
private void checkSearchSet(List<ValidationMessage> errors, Element bundle, List<Element> entries, NodeStack stack) {
// warning: should have self link
List<Element> links = new ArrayList<Element>();
bundle.getNamedChildren(LINK, links);
Element selfLink = getSelfLink(links);
List<String> types = new ArrayList<>();
if (selfLink == null) {
warning(errors, IssueType.INVALID, bundle.line(), bundle.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_SEARCH_NOSELF);
} else {
readSearchResourceTypes(selfLink.getNamedChildValue("url"), types);
if (types.size() == 0) {
hint(errors, IssueType.INVALID, bundle.line(), bundle.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_SEARCH_SELF_NOT_UNDERSTOOD);
}
}
Boolean searchMode = readHasSearchMode(entries);
if (searchMode == false) { // if no resources have search mode
boolean typeProblem = false;
String rtype = null;
int count = 0;
for (Element entry : entries) {
NodeStack estack = stack.push(entry, count, null, null);
count++;
Element res = entry.getNamedChild("resource");
if (rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), estack.getLiteralPath(), res != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE)) {
NodeStack rstack = estack.push(res, -1, null, null);
String rt = res.fhirType();
Boolean ok = checkSearchType(types, rt);
if (ok == null) {
typeProblem = true;
hint(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), selfLink == null, I18nConstants.BUNDLE_SEARCH_ENTRY_TYPE_NOT_SURE);
String id = res.getNamedChildValue("id");
warning(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null || "OperationOutcome".equals(rt), I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID);
} else if (ok) {
if (!"OperationOutcome".equals(rt)) {
String id = res.getNamedChildValue("id");
warning(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID);
if (rtype != null && !rt.equals(rtype)) {
typeProblem = true;
} else if (rtype == null) {
rtype = rt;
}
}
} else {
typeProblem = true;
warning(errors, IssueType.INVALID, bundle.line(), bundle.col(), estack.getLiteralPath(), false, I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_NO_MODE, rt, types);
}
}
}
if (typeProblem) {
warning(errors, IssueType.INVALID, bundle.line(), bundle.col(), stack.getLiteralPath(), !typeProblem, I18nConstants.BUNDLE_SEARCH_NO_MODE);
} else {
hint(errors, IssueType.INVALID, bundle.line(), bundle.col(), stack.getLiteralPath(), !typeProblem, I18nConstants.BUNDLE_SEARCH_NO_MODE);
}
} else {
int count = 0;
for (Element entry : entries) {
NodeStack estack = stack.push(entry, count, null, null);
count++;
Element res = entry.getNamedChild("resource");
String sm = null;
Element s = entry.getNamedChild("search");
if (s != null) {
sm = s.getNamedChildValue("mode");
}
warning(errors, IssueType.INVALID, bundle.line(), bundle.col(), estack.getLiteralPath(), sm != null, I18nConstants.BUNDLE_SEARCH_NO_MODE);
if (rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), estack.getLiteralPath(), res != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE)) {
NodeStack rstack = estack.push(res, -1, null, null);
String rt = res.fhirType();
String id = res.getNamedChildValue("id");
if (sm != null) {
if ("match".equals(sm)) {
rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID);
rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), types.size() == 0 || checkSearchType(types, rt), I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_MODE, rt, types);
} else if ("include".equals(sm)) {
rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID);
} else { // outcome
rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), "OperationOutcome".equals(rt), I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_OUTCOME, rt);
}
}
}
}
}
}
private Boolean checkSearchType(List<String> types, String rt) {
if (types.size() == 0) {
return null;
} else {
return Utilities.existsInList(rt, types);
}
}
private Boolean readHasSearchMode(List<Element> entries) {
boolean all = true;
boolean any = false;
for (Element entry : entries) {
String sm = null;
Element s = entry.getNamedChild("search");
if (s != null) {
sm = s.getNamedChildValue("mode");
}
if (sm != null) {
any = true;
} else {
all = false;
}
}
if (all) {
return true;
} else if (any) {
return null;
} else {
return false;
}
}
private void readSearchResourceTypes(String ref, List<String> types) {
if (ref == null) {
return;
}
String[] head = null;
String[] tail = null;
if (ref.contains("?")) {
head = ref.substring(0, ref.indexOf("?")).split("\\/");
tail = ref.substring(ref.indexOf("?")+1).split("\\&");
} else {
head = ref.split("\\/");
}
if (head == null || head.length == 0) {
return;
} else if (context.getResourceNames().contains(head[head.length-1])) {
types.add(head[head.length-1]);
} else if (tail != null) {
for (String s : tail) {
if (s.startsWith("_type=")) {
for (String t : s.substring(6).split("\\,")) {
types.add(t);
}
}
}
}
}
private Element getSelfLink(List<Element> links) {
for (Element link : links) {
if ("self".equals(link.getNamedChildValue("relation"))) {
return link;
}
}
return null;
}
private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) { private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) {
// first entry must be a composition // first entry must be a composition
if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getType().equals("Composition"), I18nConstants.BUNDLE_BUNDLE_ENTRY_DOCUMENT)) { if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getType().equals("Composition"), I18nConstants.BUNDLE_BUNDLE_ENTRY_DOCUMENT)) {