Various fixes (#348)

* depend on 1.1.42-snapshot

* Don't make a column for definitions in a code system if there are none

* special case support for fr-CA language

* Prevent NPE when auto-generating narrative and an illegal resource type is encountered

* Prevent NPE resolving resource in batch

* fix value set validation for primitive types when an expansion is provided, and the code system is not known

* FHIRPath engine: correction for allowing  boolean conversion of primitive types

* Fix handling resources in bundles when type is profiled

* Add test cases for wildcard versions

* release notes
This commit is contained in:
Grahame Grieve 2020-09-17 23:52:05 +10:00 committed by GitHub
parent 9e066e637a
commit 75921b723b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 148 additions and 66 deletions

View File

@ -0,0 +1,11 @@
Validator:
* Fix handling resources in bundles when type is profiled
* Prevent NPE resolving resource in batch
* fix value set validation for primitive types when an expansion is provided, and the code system is not known
Other Changes:
* Package Subsystem - Support wildcars for patch version
* Renderer: Don't make a column for definitions in a code system if there are none
* Renderer: special case support for fr-CA language
* Renderer: Prevent NPE when auto-generating narrative and an illegal resource type is encountered
* FHIRPath Engine: correction for allowing boolean conversion of primitive types

View File

@ -134,6 +134,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
return false;
}
XhtmlNode t = x.table( "codes");
boolean definitions = false;
boolean commentS = false;
boolean deprecated = false;
boolean display = false;
@ -161,14 +162,15 @@ public class CodeSystemRenderer extends TerminologyRenderer {
display = display || conceptsHaveDisplay(c);
version = version || conceptsHaveVersion(c);
hierarchy = hierarchy || c.hasConcept();
definitions = definitions || conceptsHaveDefinition(c);
}
CodeSystemNavigator csNav = new CodeSystemNavigator(cs);
hierarchy = hierarchy || csNav.isRestructure();
List<String> langs = new ArrayList<>();
addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, version, deprecated, properties, null, false), maps);
addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, null, false), maps);
for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs) || hasExtensions;
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, definitions, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs) || hasExtensions;
}
if (langs.size() > 0) {
Collections.sort(langs);
@ -185,6 +187,23 @@ public class CodeSystemRenderer extends TerminologyRenderer {
return hasExtensions;
}
private boolean conceptsHaveDefinition(ConceptDefinitionComponent c) {
if (c.hasDefinition()) {
return true;
}
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
return true;
}
}
for (ConceptDefinitionComponent g : c.getConcept()) {
if (conceptsHaveDefinition(g)) {
return true;
}
}
return false;
}
private boolean conceptsHaveProperty(ConceptDefinitionComponent c, PropertyComponent cp) {
if (CodeSystemUtilities.hasProperty(c, cp.getCode()))
return true;
@ -271,7 +290,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs) throws FHIRFormatError, DefinitionException, IOException {
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean hasDefinitions, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false;
XhtmlNode tr = t.tr();
XhtmlNode td = tr.td();
@ -297,36 +316,38 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (hasDisplay) {
td = tr.td();
renderDisplayName(c, cs, td);
}
td = tr.td();
if (c != null &&
c.hasDefinitionElement()) {
if (getContext().getLang() == null) {
if (hasMarkdownInDefinitions(cs))
addMarkdown(td, c.getDefinition());
else
}
if (hasDefinitions) {
td = tr.td();
if (c != null &&
c.hasDefinitionElement()) {
if (getContext().getLang() == null) {
if (hasMarkdownInDefinitions(cs))
addMarkdown(td, c.getDefinition());
else
td.addText(c.getDefinition());
} else if (getContext().getLang().equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue()))
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDefinition());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
td.br();
td.addText(cd.getLanguage()+": "+cd.getValue());
}
}
} else if (getContext().getLang().equals(cs.getLanguage()) || (getContext().getLang().equals("en") && !cs.hasLanguage())) {
td.addText(c.getDefinition());
} else if (getContext().getLang().equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue()))
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDefinition());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
td.br();
td.addText(cd.getLanguage()+": "+cd.getValue());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(getContext().getLang())) {
td.addText(cd.getValue());
}
}
}
} else if (getContext().getLang().equals(cs.getLanguage()) || (getContext().getLang().equals("en") && !cs.hasLanguage())) {
td.addText(c.getDefinition());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(getContext().getLang())) {
td.addText(cd.getValue());
}
}
}
}
}
if (deprecated) {
td = tr.td();
@ -427,7 +448,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
}
List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, comment, version, deprecated, maps, system, cs, properties, csNav, langs) || hasExtensions;
hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs) || hasExtensions;
}
for (ConceptDefinitionComponent cc : ocl) {
tr = t.tr();

View File

@ -168,6 +168,10 @@ public class DataRenderer extends Renderer {
}
protected String describeLang(String lang) {
// special cases:
if ("fr-CA".equals(lang)) {
return "French (Canadian)"; // this one was omitted from the value set
}
ValueSet v = getContext().getWorker().fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages");
if (v != null) {
ConceptReferenceComponent l = null;
@ -176,8 +180,9 @@ public class DataRenderer extends Renderer {
l = cc;
}
if (l == null) {
if (lang.contains("-"))
if (lang.contains("-")) {
lang = lang.substring(0, lang.indexOf("-"));
}
for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) {
if (cc.getCode().equals(lang) || cc.getCode().startsWith(lang+"-"))
l = cc;

View File

@ -106,13 +106,14 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
}
try {
StructureDefinition sd = r.getDefinition();
ElementDefinition ed = sd.getSnapshot().getElement().get(0);
if (sd.getType().equals("NamingSystem") && "icd10".equals(r.getId())) {
System.out.println("hah!");
if (sd == null) {
throw new FHIRException("Cannot find definition for "+r.fhirType());
} else {
ElementDefinition ed = sd.getSnapshot().getElement().get(0);
containedIds.clear();
hasExtensions = false;
generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), false, 0);
}
containedIds.clear();
hasExtensions = false;
generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), false, 0);
} catch (Exception e) {
e.printStackTrace();
x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage());

View File

@ -126,7 +126,7 @@ public class Resolver {
if (containerElement != null) {
for (org.hl7.fhir.r5.elementmodel.Element p : containerElement.getChildren("parameter")) {
org.hl7.fhir.r5.elementmodel.Element res = p.getNamedChild("resource");
if (value.equals(res.fhirType()+"/"+res.getChildValue("id")))
if (res != null && value.equals(res.fhirType()+"/"+res.getChildValue("id")))
return p;
}
}

View File

@ -164,6 +164,9 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
throw new FHIRException("Unable to evaluate based on empty code system");
}
res = validateCode(code, cs);
} else if (cs == null && valueset.hasExpansion() && inExpansion) {
// we just take the value set as face value then
res = new ValidationResult(IssueSeverity.INFORMATION, null);
} else {
// well, we didn't find a code system - try the expansion?
// disabled waiting for discussion

View File

@ -3853,7 +3853,7 @@ public class FHIRPathEngine {
} else {// (exp.getParameters().size() == 0) {
boolean all = true;
for (Base item : focus) {
Equality eq = asBool(item);
Equality eq = asBool(item, true);
if (eq != Equality.True) {
all = false;
break;
@ -4471,7 +4471,11 @@ public class FHIRPathEngine {
} else {
boolean all = true;
for (Base item : focus) {
Equality v = asBool(item);
if (!canConvertToBoolean(item)) {
throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
}
Equality v = asBool(item, true);
if (v != Equality.False) {
all = false;
break;
@ -4501,7 +4505,11 @@ public class FHIRPathEngine {
} else {
boolean any = false;
for (Base item : focus) {
Equality v = asBool(item);
if (!canConvertToBoolean(item)) {
throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
}
Equality v = asBool(item, true);
if (v == Equality.False) {
any = true;
break;
@ -4531,8 +4539,11 @@ public class FHIRPathEngine {
} else {
boolean all = true;
for (Base item : focus) {
Equality v = asBool(item);
if (v != Equality.True) {
if (!canConvertToBoolean(item)) {
throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
}
Equality v = asBool(item, true);
if (v != Equality.True) {
all = false;
break;
}
@ -4561,7 +4572,11 @@ public class FHIRPathEngine {
} else {
boolean any = false;
for (Base item : focus) {
Equality v = asBool(item);
if (!canConvertToBoolean(item)) {
throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
}
Equality v = asBool(item, true);
if (v == Equality.True) {
any = true;
break;
@ -4572,7 +4587,11 @@ public class FHIRPathEngine {
return result;
}
private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
private boolean canConvertToBoolean(Base item) {
return (item.isBooleanPrimitive());
}
private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String name = nl.get(0).primitiveValue();
if (exp.getParameters().size() == 2) {
@ -5493,8 +5512,10 @@ public class FHIRPathEngine {
private Equality asBool(List<Base> items) throws PathEngineException {
if (items.size() == 0) {
return Equality.Null;
} else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) {
return asBool(items.get(0), true);
} else if (items.size() == 1) {
return asBool(items.get(0));
return Equality.True;
} else {
throw makeException(I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items));
}
@ -5528,7 +5549,7 @@ public class FHIRPathEngine {
}
}
private Equality asBool(Base item) {
private Equality asBool(Base item, boolean narrow) {
if (item instanceof BooleanType) {
return boolToTriState(((BooleanType) item).booleanValue());
} else if (item.isBooleanPrimitive()) {
@ -5539,6 +5560,8 @@ public class FHIRPathEngine {
} else {
return Equality.Null;
}
} else if (narrow) {
return Equality.False;
} else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) {
return asBoolFromInt(item.primitiveValue());
} else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) {

View File

@ -240,7 +240,7 @@ public class VersionUtilities {
public static boolean isMajMinOrLaterPatch(String test, String current) {
String t = getMajMin(test);
String c = getMajMin(current);
if (c.compareTo(t) == 0) {
if (c != null && c.compareTo(t) == 0) {
String pt = getPatch(test);
String pc = getPatch(current);
if (pt==null || "x".equals(pt)) {

View File

@ -311,14 +311,17 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
String foundPackage = null;
String foundVersion = null;
for (String f : sorted(new File(cacheFolder).list())) {
if (f.equals(id + "#" + version) || (Utilities.noString(version) && f.startsWith(id + "#"))) {
return loadPackageInfo(Utilities.path(cacheFolder, f));
}
if (version!=null && version.endsWith(".x") && f.contains("#")) {
String[] parts = f.split("#");
if (parts[0].equals(id) && VersionUtilities.isMajMinOrLaterPatch((foundVersion!=null ? foundVersion : version),parts[1])) {
foundVersion = parts[1];
foundPackage = f;
File cf = new File(Utilities.path(cacheFolder, f));
if (cf.isDirectory()) {
if (f.equals(id + "#" + version) || (Utilities.noString(version) && f.startsWith(id + "#"))) {
return loadPackageInfo(Utilities.path(cacheFolder, f));
}
if (version != null && version.endsWith(".x") && f.contains("#")) {
String[] parts = f.split("#");
if (parts[0].equals(id) && VersionUtilities.isMajMinOrLaterPatch((foundVersion!=null ? foundVersion : version),parts[1])) {
foundVersion = parts[1];
foundPackage = f;
}
}
}
}

View File

@ -10,8 +10,8 @@ Bundle_BUNDLE_Entry_NoFullUrl = Bundle entry missing fullUrl
Bundle_BUNDLE_Entry_NoProfile = No profile found for contained resource of type ''{0}''
Bundle_BUNDLE_Entry_NotFound = Can''t find ''{0}'' in the bundle ({1})
Bundle_BUNDLE_Entry_Orphan = Entry {0} isn''t reachable by traversing from first Bundle entry
Bundle_BUNDLE_Entry_Type = The type ''{0}'' is not valid - no resources allowed here
Bundle_BUNDLE_Entry_Type2 = The type ''{0}'' is not valid - must be {1}
Bundle_BUNDLE_Entry_Type = The type ''{0}'' is not valid - no resources allowed here (allowed = {1})
Bundle_BUNDLE_Entry_Type2 = The type ''{0}'' is not valid - must be {1} (allowed = {2})
Bundle_BUNDLE_Entry_Type3 = The type ''{0}'' is not valid - must be one of {1}
Bundle_BUNDLE_FullUrl_Missing = Relative Reference appears inside Bundle whose entry is missing a fullUrl
Bundle_BUNDLE_FullUrl_NeedVersion = Entries matching fullURL {0} should declare meta/versionId because there are version-specific references
@ -156,9 +156,9 @@ Terminology_TX_NoValid_17 = The value provided (''{0}'') is not in the value set
Terminology_TX_NoValid_18 = The value provided (''{0}'') is not in the value set {1} ({2}), and a code is recommended to come from this value set){3}
Terminology_TX_NoValid_2 = None of the codes provided are in the value set {0} ({1}), and a code should come from this value set unless it has no suitable code) (codes = {2})
Terminology_TX_NoValid_3 = None of the codes provided are in the value set {0} ({1}), and a code is recommended to come from this value set) (codes = {2})
Terminology_TX_NoValid_4 = The Coding provided ({2}) is not in the value set {0}, and a code is required from this value set{1}
Terminology_TX_NoValid_5 = The Coding provided ({2}) is not in the value set {0}, and a code should come from this value set unless it has no suitable code{1}
Terminology_TX_NoValid_6 = The Coding provided ({2}) is not in the value set {0}, and a code is recommended to come from this value set{1}
Terminology_TX_NoValid_4 = The Coding provided ({2}) is not in the value set {0}, and a code is required from this value set {1}
Terminology_TX_NoValid_5 = The Coding provided ({2}) is not in the value set {0}, and a code should come from this value set unless it has no suitable code {1}
Terminology_TX_NoValid_6 = The Coding provided ({2}) is not in the value set {0}, and a code is recommended to come from this value set {1}
Terminology_TX_NoValid_7 = None of the codes provided could be validated against the maximum value set {0} ({1}), (error = {2})
Terminology_TX_NoValid_8 = None of the codes provided are in the maximum value set {0} ({1}), and a code from this value set is required) (codes = {2})
Terminology_TX_NoValid_9 = The code provided could not be validated against the maximum value set {0} ({1}), (error = {2})

View File

@ -37,4 +37,14 @@ public class PackageCacheTests {
list = cache.listPackages();
Assertions.assertFalse(list.isEmpty());
}
@Test
public void testPatchWildCard() throws IOException {
FilesystemPackageCacheManager cache = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
cache.clear();
Assertions.assertEquals(cache.loadPackage("hl7.fhir.us.core", "3.1.0").version(), "3.1.0");
Assertions.assertEquals(cache.loadPackage("hl7.fhir.us.core", "3.1.1").version(), "3.1.1");
Assertions.assertEquals(cache.loadPackage("hl7.fhir.us.core", "3.1.x").version(), "3.1.1");
Assertions.assertEquals(cache.loadPackage("hl7.fhir.us.core", "3.0.x").version(), "3.0.1");
}
}

View File

@ -3943,15 +3943,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException {
String resourceName = element.getType();
TypeRefComponent trr = null;
CommaSeparatedStringBuilder bt = new CommaSeparatedStringBuilder();
for (TypeRefComponent tr : child.getType()) {
if (tr.getCode().equals("Resource")) {
bt.append(tr.getCode());
if (tr.getCode().equals("Resource") || tr.getCode().equals(resourceName) ) {
trr = tr;
break;
}
}
stack.qualifyPath(".ofType("+resourceName+")");
if (trr == null) {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName);
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName, bt.toString());
} else if (isValidResourceType(resourceName, trr)) {
// special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
ValidatorHostContext hc = null;
@ -4002,7 +4004,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private boolean isValidResourceType(String type, TypeRefComponent def) {
if (!def.hasProfile()) {
if (!def.hasProfile() && def.getCode().equals("Resource")) {
return true;
}
if (def.getCode().equals(type)) {
return true;
}
List<StructureDefinition> list = new ArrayList<>();

View File

@ -17,7 +17,7 @@
<properties>
<hapi_fhir_version>5.1.0</hapi_fhir_version>
<validator_test_case_version>1.1.41</validator_test_case_version>
<validator_test_case_version>1.1.42-SNAPSHOT</validator_test_case_version>
<junit_jupiter_version>5.6.2</junit_jupiter_version>
<maven_surefire_version>3.0.0-M4</maven_surefire_version>
<jacoco_version>0.8.5</jacoco_version>