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:
commit
8550fc37b8
|
@ -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
|
|
@ -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'
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
Loading…
Reference in New Issue