Merge pull request #1160 from hapifhir/gg-202303-qa-sm-again

More fixes for structure map validation
This commit is contained in:
Grahame Grieve 2023-03-09 11:55:44 +11:00 committed by GitHub
commit f3572b9b8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 95 deletions

View File

@ -2150,6 +2150,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} }
} }
@Override
public List<StructureDefinition> fetchTypeDefinitions(String typeName) {
List<StructureDefinition> res = new ArrayList<>();
structures.listAll(res);
res.removeIf(sd -> !sd.getType().equals(typeName));
return res;
}
public boolean isTlogging() { public boolean isTlogging() {
return tlogging; return tlogging;
} }

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.r5.context;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection;
import java.util.Date; import java.util.Date;
/* /*
@ -736,13 +737,21 @@ public interface IWorkerContext {
/** /**
* This is a short cut for fetchResource(StructureDefinition.class, ...) * This is a short cut for fetchResource(StructureDefinition.class, ...)
* but it accepts a typename - that is, it resolves based on StructureDefinition.type * but it accepts a typename - that is, it resolves based on StructureDefinition.type
* or StructureDefinition.url * or StructureDefinition.url. This only resolves to http://hl7.org/fhir/StructureDefinition/{typename}
* *
* @param typeName * @param typeName
* @return * @return
*/ */
public StructureDefinition fetchTypeDefinition(String typeName); public StructureDefinition fetchTypeDefinition(String typeName);
/**
* This finds all the structure definitions that have the given typeName
*
* @param typeName
* @return
*/
public List<StructureDefinition> fetchTypeDefinitions(String n);
/** /**
* Returns a set of keys that can be used to get binaries from this context. * Returns a set of keys that can be used to get binaries from this context.

View File

@ -1340,6 +1340,29 @@ public String toString() {
return getExpression()+" "+getDiagnostics()+" "+getSeverity().toCode()+"/"+getCode().toCode()+": "+getDetails().getText(); return getExpression()+" "+getDiagnostics()+" "+getSeverity().toCode()+"/"+getCode().toCode()+": "+getDetails().getText();
} }
} }
public boolean isWarningOrMore() {
switch (getSeverity()) {
case FATAL: return true;
case ERROR: return true;
case WARNING: return true;
case INFORMATION: return false;
case SUCCESS: return false;
case NULL: return false;
default: return false;
}
}
public boolean isInformationorLess() {
switch (getSeverity()) {
case FATAL: return false;
case ERROR: return true;
case WARNING: return false;
case INFORMATION: return true;
case SUCCESS: return true;
case NULL: return true;
default: return false;
}
}
// end addition // end addition
} }
@ -1543,6 +1566,19 @@ public String toString() {
return ResourceType.OperationOutcome; return ResourceType.OperationOutcome;
} }
public boolean isSuccess() {
for (OperationOutcomeIssueComponent iss : getIssue()) {
if (iss.isWarningOrMore() || iss.getCode() != IssueType.INFORMATIONAL) {
return false;
}
if (iss.isInformationorLess() || iss.getCode() != IssueType.INFORMATIONAL) {
return true;
}
}
return false;
}
} }

View File

@ -229,22 +229,34 @@ public class TypeDetails {
tail = n.substring( n.indexOf("#")+1); tail = n.substring( n.indexOf("#")+1);
tail = tail.substring(tail.indexOf(".")); tail = tail.substring(tail.indexOf("."));
} }
String t = ProfiledType.ns(n); List<StructureDefinition> list = new ArrayList<>();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, t); if (!Utilities.isAbsoluteUrl(n)) {
while (sd != null) { list.addAll(context.fetchTypeDefinitions(n));
if (tail == null && typesContains(sd.getUrl())) } else {
return true; String t = ProfiledType.ns(n);
if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl()))) StructureDefinition sd = context.fetchResource(StructureDefinition.class, t);
return true; if (sd != null) {
if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail)) list.add(sd);
return true; }
if (sd.hasBaseDefinition()) { }
if (sd.getType().equals("uri")) for (int i = 0; i < list.size(); i++) {
sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string"); StructureDefinition sd = list.get(i);
else while (sd != null) {
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); if (tail == null && typesContains(sd.getUrl()))
} else return true;
sd = null; if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl())))
return true;
if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail))
return true;
if (sd.hasBaseDefinition()) {
if (sd.getType().equals("uri"))
sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string");
else
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
} else {
sd = null;
}
}
} }
} }
return false; return false;

View File

@ -6045,7 +6045,12 @@ public class FHIRPathEngine {
} }
private boolean isAbstractType(List<TypeRefComponent> list) { private boolean isAbstractType(List<TypeRefComponent> list) {
return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); if (list.size() != 1) {
return false;
} else {
StructureDefinition sd = worker.fetchTypeDefinition(list.get(0).getCode());
return sd != null && sd.getAbstract();
}
} }
private boolean hasType(ElementDefinition ed, String s) { private boolean hasType(ElementDefinition ed, String s) {

View File

@ -1,8 +1,32 @@
package org.hl7.fhir.r5.utils.structuremap; package org.hl7.fhir.r5.utils.structuremap;
import org.hl7.fhir.r5.model.StructureMap; import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent;
public class ResolvedGroup { public class ResolvedGroup {
public StructureMap.StructureMapGroupComponent target; private StructureMap.StructureMapGroupComponent targetGroup;
public StructureMap targetMap; private StructureMap targetMap;
public ResolvedGroup(StructureMap targetMap, StructureMapGroupComponent targetGroup) {
super();
this.targetMap = targetMap;
this.targetGroup = targetGroup;
}
public StructureMap.StructureMapGroupComponent getTargetGroup() {
return targetGroup;
}
public StructureMap getTargetMap() {
return targetMap;
}
public void setTargetGroup(StructureMap.StructureMapGroupComponent targetGroup) {
this.targetGroup = targetGroup;
}
public void setTargetMap(StructureMap targetMap) {
this.targetMap = targetMap;
}
} }

View File

@ -1243,7 +1243,7 @@ public class StructureMapUtilities {
// todo: check inputs // todo: check inputs
if (group.hasExtends()) { if (group.hasExtends()) {
ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends());
executeGroup(indent + " ", context, rg.targetMap, vars, rg.target, false); executeGroup(indent + " ", context, rg.getTargetMap(), vars, rg.getTargetGroup(), false);
} }
for (StructureMapGroupRuleComponent r : group.getRule()) { for (StructureMapGroupRuleComponent r : group.getRule()) {
@ -1279,9 +1279,9 @@ public class StructureMapUtilities {
String tgtType = tgt.fhirType(); String tgtType = tgt.fhirType();
ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType);
Variables vdef = new Variables(); Variables vdef = new Variables();
vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); vdef.add(VariableMode.INPUT, defGroup.getTargetGroup().getInput().get(0).getName(), src);
vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); vdef.add(VariableMode.OUTPUT, defGroup.getTargetGroup().getInput().get(1).getName(), tgt);
executeGroup(indent + " ", context, defGroup.targetMap, vdef, defGroup.target, false); executeGroup(indent + " ", context, defGroup.getTargetMap(), vdef, defGroup.getTargetGroup(), false);
} }
} }
} }
@ -1290,12 +1290,12 @@ public class StructureMapUtilities {
private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException {
ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName());
if (rg.target.getInput().size() != dependent.getParameter().size()) { if (rg.getTargetGroup().getInput().size() != dependent.getParameter().size()) {
throw new FHIRException("Rule '" + dependent.getName() + "' has " + rg.target.getInput().size() + " but the invocation has " + dependent.getParameter().size() + " variables"); throw new FHIRException("Rule '" + dependent.getName() + "' has " + rg.getTargetGroup().getInput().size() + " but the invocation has " + dependent.getParameter().size() + " variables");
} }
Variables v = new Variables(); Variables v = new Variables();
for (int i = 0; i < rg.target.getInput().size(); i++) { for (int i = 0; i < rg.getTargetGroup().getInput().size(); i++) {
StructureMapGroupInputComponent input = rg.target.getInput().get(i); StructureMapGroupInputComponent input = rg.getTargetGroup().getInput().get(i);
StructureMapGroupRuleTargetParameterComponent rdp = dependent.getParameter().get(i); StructureMapGroupRuleTargetParameterComponent rdp = dependent.getParameter().get(i);
String var = rdp.getValue().primitiveValue(); String var = rdp.getValue().primitiveValue();
VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT;
@ -1306,7 +1306,7 @@ public class StructureMapUtilities {
throw new FHIRException("Rule '" + dependent.getName() + "' " + mode.toString() + " variable '" + input.getName() + "' named as '" + var + "' has no value (vars = " + vin.summary() + ")"); throw new FHIRException("Rule '" + dependent.getName() + "' " + mode.toString() + " variable '" + input.getName() + "' named as '" + var + "' has no value (vars = " + vin.summary() + ")");
v.add(mode, input.getName(), vv); v.add(mode, input.getName(), vv);
} }
executeGroup(indent + " ", context, rg.targetMap, v, rg.target, false); executeGroup(indent + " ", context, rg.getTargetMap(), v, rg.getTargetGroup(), false);
} }
private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException {
@ -1315,20 +1315,18 @@ public class StructureMapUtilities {
if (source.hasUserData(kn)) if (source.hasUserData(kn))
return source.getUserString(kn); return source.getUserString(kn);
ResolvedGroup res = new ResolvedGroup(); ResolvedGroup res = new ResolvedGroup(null, null);
res.targetMap = null;
res.target = null;
for (StructureMapGroupComponent grp : map.getGroup()) { for (StructureMapGroupComponent grp : map.getGroup()) {
if (matchesByType(map, grp, type)) { if (matchesByType(map, grp, type)) {
if (res.targetMap == null) { if (res.getTargetMap() == null) {
res.targetMap = map; res.setTargetMap(map);
res.target = grp; res.setTargetGroup(grp);
} else } else
throw new FHIRException("Multiple possible matches looking for default rule for '" + type + "'"); throw new FHIRException("Multiple possible matches looking for default rule for '" + type + "'");
} }
} }
if (res.targetMap != null) { if (res.getTargetMap() != null) {
String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); String result = getActualType(res.getTargetMap(), res.getTargetGroup().getInput().get(1).getType());
source.setUserData(kn, result); source.setUserData(kn, result);
return result; return result;
} }
@ -1341,19 +1339,19 @@ public class StructureMapUtilities {
if (!impMap.getUrl().equals(map.getUrl())) { if (!impMap.getUrl().equals(map.getUrl())) {
for (StructureMapGroupComponent grp : impMap.getGroup()) { for (StructureMapGroupComponent grp : impMap.getGroup()) {
if (matchesByType(impMap, grp, type)) { if (matchesByType(impMap, grp, type)) {
if (res.targetMap == null) { if (res.getTargetMap() == null) {
res.targetMap = impMap; res.setTargetMap(impMap);
res.target = grp; res.setTargetGroup(grp);
} else } else
throw new FHIRException("Multiple possible matches for default rule for '" + type + "' in " + res.targetMap.getUrl() + " (" + res.target.getName() + ") and " + impMap.getUrl() + " (" + grp.getName() + ")"); throw new FHIRException("Multiple possible matches for default rule for '" + type + "' in " + res.getTargetMap().getUrl() + " (" + res.getTargetGroup().getName() + ") and " + impMap.getUrl() + " (" + grp.getName() + ")");
} }
} }
} }
} }
} }
if (res.target == null) if (res.getTargetGroup() == null)
throw new FHIRException("No matches found for default rule for '" + type + "' from " + map.getUrl()); throw new FHIRException("No matches found for default rule for '" + type + "' from " + map.getUrl());
String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... String result = getActualType(res.getTargetMap(), res.getTargetGroup().getInput().get(1).getType()); // should be .getType, but R2...
source.setUserData(kn, result); source.setUserData(kn, result);
return result; return result;
} }
@ -1390,19 +1388,17 @@ public class StructureMapUtilities {
if (source.hasUserData(kn)) if (source.hasUserData(kn))
return (ResolvedGroup) source.getUserData(kn); return (ResolvedGroup) source.getUserData(kn);
ResolvedGroup res = new ResolvedGroup(); ResolvedGroup res = new ResolvedGroup(null, null);
res.targetMap = null;
res.target = null;
for (StructureMapGroupComponent grp : map.getGroup()) { for (StructureMapGroupComponent grp : map.getGroup()) {
if (matchesByType(map, grp, srcType, tgtType)) { if (matchesByType(map, grp, srcType, tgtType)) {
if (res.targetMap == null) { if (res.getTargetMap() == null) {
res.targetMap = map; res.setTargetMap(map);
res.target = grp; res.setTargetGroup(grp);
} else } else
throw new FHIRException("Multiple possible matches looking for rule for '" + srcType + "/" + tgtType + "', from rule '" + ruleid + "'"); throw new FHIRException("Multiple possible matches looking for rule for '" + srcType + "/" + tgtType + "', from rule '" + ruleid + "'");
} }
} }
if (res.targetMap != null) { if (res.getTargetMap() != null) {
source.setUserData(kn, res); source.setUserData(kn, res);
return res; return res;
} }
@ -1415,17 +1411,17 @@ public class StructureMapUtilities {
if (!impMap.getUrl().equals(map.getUrl())) { if (!impMap.getUrl().equals(map.getUrl())) {
for (StructureMapGroupComponent grp : impMap.getGroup()) { for (StructureMapGroupComponent grp : impMap.getGroup()) {
if (matchesByType(impMap, grp, srcType, tgtType)) { if (matchesByType(impMap, grp, srcType, tgtType)) {
if (res.targetMap == null) { if (res.getTargetMap() == null) {
res.targetMap = impMap; res.setTargetMap(impMap);
res.target = grp; res.setTargetGroup(grp);
} else } else
throw new FHIRException("Multiple possible matches for rule for '" + srcType + "/" + tgtType + "' in " + res.targetMap.getUrl() + " and " + impMap.getUrl() + ", from rule '" + ruleid + "'"); throw new FHIRException("Multiple possible matches for rule for '" + srcType + "/" + tgtType + "' in " + res.getTargetMap().getUrl() + " and " + impMap.getUrl() + ", from rule '" + ruleid + "'");
} }
} }
} }
} }
} }
if (res.target == null) if (res.getTargetGroup() == null)
throw new FHIRException("No matches found for rule for '" + srcType + " to " + tgtType + "' from " + map.getUrl() + ", from rule '" + ruleid + "'"); throw new FHIRException("No matches found for rule for '" + srcType + " to " + tgtType + "' from " + map.getUrl() + ", from rule '" + ruleid + "'");
source.setUserData(kn, res); source.setUserData(kn, res);
return res; return res;
@ -1493,19 +1489,17 @@ public class StructureMapUtilities {
if (source.hasUserData(kn)) if (source.hasUserData(kn))
return (ResolvedGroup) source.getUserData(kn); return (ResolvedGroup) source.getUserData(kn);
ResolvedGroup res = new ResolvedGroup(); ResolvedGroup res = new ResolvedGroup(null, null);
res.targetMap = null;
res.target = null;
for (StructureMapGroupComponent grp : map.getGroup()) { for (StructureMapGroupComponent grp : map.getGroup()) {
if (grp.getName().equals(name)) { if (grp.getName().equals(name)) {
if (res.targetMap == null) { if (res.getTargetMap() == null) {
res.targetMap = map; res.setTargetMap(map);
res.target = grp; res.setTargetGroup(grp);
} else } else
throw new FHIRException("Multiple possible matches for rule '" + name + "'"); throw new FHIRException("Multiple possible matches for rule '" + name + "'");
} }
} }
if (res.targetMap != null) { if (res.getTargetMap() != null) {
source.setUserData(kn, res); source.setUserData(kn, res);
return res; return res;
} }
@ -1518,19 +1512,19 @@ public class StructureMapUtilities {
if (!impMap.getUrl().equals(map.getUrl())) { if (!impMap.getUrl().equals(map.getUrl())) {
for (StructureMapGroupComponent grp : impMap.getGroup()) { for (StructureMapGroupComponent grp : impMap.getGroup()) {
if (grp.getName().equals(name)) { if (grp.getName().equals(name)) {
if (res.targetMap == null) { if (res.getTargetMap() == null) {
res.targetMap = impMap; res.setTargetMap(impMap);
res.target = grp; res.setTargetGroup(grp);
} else } else
throw new FHIRException("Multiple possible matches for rule group '" + name + "' in " + throw new FHIRException("Multiple possible matches for rule group '" + name + "' in " +
res.targetMap.getUrl() + "#" + res.target.getName() + " and " + res.getTargetMap().getUrl() + "#" + res.getTargetGroup().getName() + " and " +
impMap.getUrl() + "#" + grp.getName()); impMap.getUrl() + "#" + grp.getName());
} }
} }
} }
} }
} }
if (res.target == null) if (res.getTargetGroup() == null)
throw new FHIRException("No matches found for rule '" + name + "'. Reference found in " + map.getUrl()); throw new FHIRException("No matches found for rule '" + name + "'. Reference found in " + map.getUrl());
source.setUserData(kn, res); source.setUserData(kn, res);
return res; return res;

View File

@ -846,6 +846,7 @@ public class I18nConstants {
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM"; public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID = "CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID"; public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID = "CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM"; public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM";
public static final String SM_GROUP_NAME_DUPLICATE = "SM_GROUP_NAME_DUPLICATE";
} }

View File

@ -577,7 +577,7 @@ FHIRPATH_LOCATION = (at {0})
FHIRPATH_UNKNOWN_CONTEXT = Unknown context evaluating FHIRPath expression: {0} FHIRPATH_UNKNOWN_CONTEXT = Unknown context evaluating FHIRPath expression: {0}
FHIRPATH_UNKNOWN_CONTEXT_ELEMENT = Unknown context element evaluating FHIRPath expression: {0} FHIRPATH_UNKNOWN_CONTEXT_ELEMENT = Unknown context element evaluating FHIRPath expression: {0}
FHIRPATH_ALIAS_COLLECTION = Attempt to alias a collection, not a singleton evaluating FHIRPath expression FHIRPATH_ALIAS_COLLECTION = Attempt to alias a collection, not a singleton evaluating FHIRPath expression
FHIRPATH_UNKNOWN_NAME = Error evaluating FHIRPath expression: The name {0} is not valid for any of the possible types: {1} FHIRPATH_UNKNOWN_NAME = Error evaluating FHIRPath expression: The name ''{0}'' is not valid for any of the possible types: {1}
FHIRPATH_UNKNOWN_CONSTANT = Error evaluating FHIRPath expression: Invalid FHIR Constant {0} FHIRPATH_UNKNOWN_CONSTANT = Error evaluating FHIRPath expression: Invalid FHIR Constant {0}
FHIRPATH_CANNOT_USE = Error evaluating FHIRPath expression: Cannot use {0} in this context because {1} FHIRPATH_CANNOT_USE = Error evaluating FHIRPath expression: Cannot use {0} in this context because {1}
FHIRPATH_CANT_COMPARE = Error evaluating FHIRPath expression: Unable to compare values of type {0} and {1} FHIRPATH_CANT_COMPARE = Error evaluating FHIRPath expression: Unable to compare values of type {0} and {1}
@ -834,6 +834,7 @@ SD_NO_SLICING_ON_ROOT = Slicing is not allowed at the root of a profile
REFERENCE_REF_QUERY_INVALID = The query part of the conditional reference is not a valid query string ({0}) REFERENCE_REF_QUERY_INVALID = The query part of the conditional reference is not a valid query string ({0})
SM_RULEGROUP_NOT_FOUND = The group {0} could not be resolved SM_RULEGROUP_NOT_FOUND = The group {0} could not be resolved
SM_NAME_INVALID = The name {0} is not valid SM_NAME_INVALID = The name {0} is not valid
SM_GROUP_NAME_DUPLICATE = The Group name ''{0}'' is already used
SM_GROUP_INPUT_DUPLICATE = The name {0} is already used SM_GROUP_INPUT_DUPLICATE = The name {0} is already used
SM_GROUP_INPUT_MODE_INVALID = The group parameter {0} mode {1} isn''t valid SM_GROUP_INPUT_MODE_INVALID = The group parameter {0} mode {1} isn''t valid
SM_GROUP_INPUT_NO_TYPE = The group parameter {0} has no type, so the paths cannot be validated SM_GROUP_INPUT_NO_TYPE = The group parameter {0} has no type, so the paths cannot be validated
@ -846,7 +847,7 @@ SM_SOURCE_PATH_INVALID = The source path {0}.{1} refers to the path {2} which is
SM_RULE_SOURCE_MIN_REDUNDANT = The min value of {0} is redundant since the valid min is {0} SM_RULE_SOURCE_MIN_REDUNDANT = The min value of {0} is redundant since the valid min is {0}
SM_RULE_SOURCE_MAX_REDUNDANT = The max value of {0} is redundant since the valid max is {0} SM_RULE_SOURCE_MAX_REDUNDANT = The max value of {0} is redundant since the valid max is {0}
SM_RULE_SOURCE_LISTMODE_REDUNDANT = The listMode value of {0} is redundant since the valid max is {0} SM_RULE_SOURCE_LISTMODE_REDUNDANT = The listMode value of {0} is redundant since the valid max is {0}
SM_TARGET_CONTEXT_UNKNOWN = The target context {0} is not known at this point SM_TARGET_CONTEXT_UNKNOWN = The target context ''{0}'' is not known at this point
SM_TARGET_PATH_INVALID = The target path {0}.{1} refers to the path {2} which is unknown SM_TARGET_PATH_INVALID = The target path {0}.{1} refers to the path {2} which is unknown
SM_NO_LIST_MODE_NEEDED = A list mode should not be provided since this is a rule that can only be executed once SM_NO_LIST_MODE_NEEDED = A list mode should not be provided since this is a rule that can only be executed once
SM_NO_LIST_RULE_ID_NEEDED = A list ruleId should not be provided since this is a rule that can only be executed once SM_NO_LIST_RULE_ID_NEEDED = A list ruleId should not be provided since this is a rule that can only be executed once
@ -864,7 +865,7 @@ SM_TARGET_TRANSFORM_EXPRESSION_ERROR = The FHIRPath expression passed as the eva
SM_IMPORT_NOT_FOUND = No maps were found to match {0} - validation may be wrong SM_IMPORT_NOT_FOUND = No maps were found to match {0} - validation may be wrong
SM_TARGET_TYPE_MULTIPLE_POSSIBLE = Multiple types are possible here ({0}) so further type checking is not possible SM_TARGET_TYPE_MULTIPLE_POSSIBLE = Multiple types are possible here ({0}) so further type checking is not possible
SM_DEPENDENT_PARAM_MODE_MISMATCH = The parameter {0} refers to the variable {1} but it''s mode is {2} which is not the same as the mode required for the group {3} SM_DEPENDENT_PARAM_MODE_MISMATCH = The parameter {0} refers to the variable {1} but it''s mode is {2} which is not the same as the mode required for the group {3}
SM_DEPENDENT_PARAM_TYPE_MISMATCH = The parameter {0} refers to the variable {1} but it''s type is {2} which is not compatible with the type required for the group {3} SM_DEPENDENT_PARAM_TYPE_MISMATCH = The parameter ''{0}'' refers to the variable ''{1}'' but it''s type is ''{2}'' which is not compatible with the type required for the group ''{3}'', which is ''{4}'' (from map ''{5}'')
SM_ORPHAN_GROUP = The group {0} is not called from within this mapping script, and does not have types on it's inputs, so type verification is not possible SM_ORPHAN_GROUP = The group {0} is not called from within this mapping script, and does not have types on it's inputs, so type verification is not possible
SM_SOURCE_TYPE_NOT_FOUND = No source type was found, so the default group for this implied dependent rule could not be determined SM_SOURCE_TYPE_NOT_FOUND = No source type was found, so the default group for this implied dependent rule could not be determined
SM_TARGET_TYPE_NOT_FOUND = No target type was found, so the default group for this implied dependent rule could not be determined SM_TARGET_TYPE_NOT_FOUND = No target type was found, so the default group for this implied dependent rule could not be determined

View File

@ -26,17 +26,29 @@ public class CompactRenderer extends ValidationOutputRenderer {
@Override @Override
public void render(OperationOutcome op) throws IOException { public void render(OperationOutcome op) throws IOException {
if (split) { if (split) {
String file = Utilities.changeFileExt(tail(ToolingExtensions.readStringExtension(op, ToolingExtensions.EXT_OO_FILE)), ".txt"); File file = new File(Utilities.path(dir.getAbsolutePath(), Utilities.changeFileExt(tail(ToolingExtensions.readStringExtension(op, ToolingExtensions.EXT_OO_FILE)), ".txt")));
PrintStream dstF = new PrintStream(new FileOutputStream(Utilities.path(dir.getAbsolutePath(), file))); if (op.isSuccess()) {
render(dstF, op); if (file.exists()) {
dstF.close(); file.delete();
}
} else {
PrintStream dstF = new PrintStream(new FileOutputStream(file));
render(dstF, op);
dstF.close();
}
} else { } else {
render(dst, op); render(dst, op);
} }
} }
private void render(PrintStream d, OperationOutcome op) { private void render(PrintStream d, OperationOutcome op) {
d.println(ToolingExtensions.readStringExtension(op, ToolingExtensions.EXT_OO_FILE)+" "+getRunDate()); if (split) {
d.println(new File(ToolingExtensions.readStringExtension(op, ToolingExtensions.EXT_OO_FILE)).getName()+" "+getRunDate()+":");
} else {
d.println();
d.println("----------------------------------------------------------------------------------");
d.println(ToolingExtensions.readStringExtension(op, ToolingExtensions.EXT_OO_FILE)+" "+getRunDate());
}
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
for (OperationOutcome.OperationOutcomeIssueComponent issue : op.getIssue()) { for (OperationOutcome.OperationOutcomeIssueComponent issue : op.getIssue()) {
String path = issue.hasExpression() ? issue.getExpression().get(0).asStringValue() : "n/a"; String path = issue.hasExpression() ? issue.getExpression().get(0).asStringValue() : "n/a";
@ -48,6 +60,10 @@ public class CompactRenderer extends ValidationOutputRenderer {
for (String s : lines) { for (String s : lines) {
d.println(s.substring(s.indexOf("|")+1)); d.println(s.substring(s.indexOf("|")+1));
} }
if (split) {
} else {
d.println();
}
} }
private String tail(String n) { private String tail(String n) {

View File

@ -33,6 +33,7 @@ import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities; import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.structuremap.ResolvedGroup;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
@ -173,6 +174,8 @@ public class StructureMapValidator extends BaseValidator {
if (ed != null) { if (ed != null) {
if (!ed.getPath().contains(".")) { if (!ed.getPath().contains(".")) {
return ed.getPath(); return ed.getPath();
} else if (isAbstractType(ed.getType())) {
return sd.getUrl()+"#"+ed.getPath();
} else if (ed.getType().size() == 1) { } else if (ed.getType().size() == 1) {
return ed.getType().get(0).getWorkingCode(); return ed.getType().get(0).getWorkingCode();
} }
@ -305,6 +308,16 @@ public class StructureMapValidator extends BaseValidator {
} }
public boolean isAbstractType(List<TypeRefComponent> list) {
if (list.size() != 1) {
return false;
} else {
StructureDefinition sd = context.fetchTypeDefinition(list.get(0).getCode());
return sd != null && sd.getAbstract();
}
}
public boolean validateStructureMap(List<ValidationMessage> errors, Element src, NodeStack stack) { public boolean validateStructureMap(List<ValidationMessage> errors, Element src, NodeStack stack) {
boolean ok = true; boolean ok = true;
List<Element> imports = src.getChildrenByName("import"); List<Element> imports = src.getChildrenByName("import");
@ -314,6 +327,7 @@ public class StructureMapValidator extends BaseValidator {
cc++; cc++;
} }
List<String> grpNames = new ArrayList<>();
List<Element> groups = src.getChildrenByName("group"); List<Element> groups = src.getChildrenByName("group");
// we iterate the groups repeatedly, validating them if they have stated types or found types, until nothing happens // we iterate the groups repeatedly, validating them if they have stated types or found types, until nothing happens
boolean fired = false; boolean fired = false;
@ -325,7 +339,7 @@ public class StructureMapValidator extends BaseValidator {
if (hasInputTypes(group) || group.hasUserData("structuremap.parameters")) { if (hasInputTypes(group) || group.hasUserData("structuremap.parameters")) {
group.setUserData("structuremap.validated", true); group.setUserData("structuremap.validated", true);
fired = true; fired = true;
ok = validateGroup(errors, src, group, stack.push(group, cc, null, null)) && ok; ok = validateGroup(errors, src, group, stack.push(group, cc, null, null), grpNames) && ok;
} }
} }
cc++; cc++;
@ -336,7 +350,7 @@ public class StructureMapValidator extends BaseValidator {
for (Element group : groups) { for (Element group : groups) {
if (!group.hasUserData("structuremap.validated")) { if (!group.hasUserData("structuremap.validated")) {
hint(errors, "2023-03-01", IssueType.INFORMATIONAL, group.line(), group.col(), stack.push(group, cc, null, null).getLiteralPath(), ok, I18nConstants.SM_ORPHAN_GROUP, group.getChildValue("name")); hint(errors, "2023-03-01", IssueType.INFORMATIONAL, group.line(), group.col(), stack.push(group, cc, null, null).getLiteralPath(), ok, I18nConstants.SM_ORPHAN_GROUP, group.getChildValue("name"));
ok = validateGroup(errors, src, group, stack.push(group, cc, null, null)) && ok; ok = validateGroup(errors, src, group, stack.push(group, cc, null, null), grpNames) && ok;
} }
cc++; cc++;
} }
@ -369,13 +383,16 @@ public class StructureMapValidator extends BaseValidator {
return true; return true;
} }
private boolean validateGroup(List<ValidationMessage> errors, Element src, Element group, NodeStack stack) { private boolean validateGroup(List<ValidationMessage> errors, Element src, Element group, NodeStack stack, List<String> grpNames) {
String name = group.getChildValue("name"); String name = group.getChildValue("name");
boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name); boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name);
if (!rule(errors, "2023-03-01", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), !grpNames.contains(name), I18nConstants.SM_GROUP_NAME_DUPLICATE, name)) {
grpNames.add(name);
}
Element extend = group.getNamedChild("extends"); Element extend = group.getNamedChild("extends");
if (extend != null) { if (extend != null) {
StructureMapGroupComponent grp = resolveGroup(extend.primitiveValue(), src); ResolvedGroup grp = resolveGroup(extend.primitiveValue(), src);
if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, extend.line(), extend.col(), stack.push(extend, -1, null, null).getLiteralPath(), grp != null, I18nConstants.SM_RULEGROUP_NOT_FOUND, extend.primitiveValue())) { if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, extend.line(), extend.col(), stack.push(extend, -1, null, null).getLiteralPath(), grp != null, I18nConstants.SM_RULEGROUP_NOT_FOUND, extend.primitiveValue())) {
// check inputs // check inputs
} else { } else {
@ -406,7 +423,7 @@ public class StructureMapValidator extends BaseValidator {
return ok; return ok;
} }
private StructureMapGroupComponent resolveGroup(String grpName, Element src) { private ResolvedGroup resolveGroup(String grpName, Element src) {
if (grpName == null) { if (grpName == null) {
return null; return null;
} }
@ -414,13 +431,13 @@ public class StructureMapValidator extends BaseValidator {
for (Element group : groups) { for (Element group : groups) {
String name = group.getChildValue("name"); String name = group.getChildValue("name");
if (grpName.equals(name)) { if (grpName.equals(name)) {
return makeGroupComponent(group); return new ResolvedGroup(null, makeGroupComponent(group));
} }
} }
for (StructureMap map : imports) { for (StructureMap map : imports) {
for (StructureMapGroupComponent grp : map.getGroup()) { for (StructureMapGroupComponent grp : map.getGroup()) {
if (grpName.equals(grp.getName())) { if (grpName.equals(grp.getName())) {
return grp; return new ResolvedGroup(map, grp);
} }
} }
} }
@ -724,7 +741,7 @@ public class StructureMapValidator extends BaseValidator {
String exp = params.get(0).getChildValue("value"); String exp = params.get(0).getChildValue("value");
if (rule(errors, "2023-03-01", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), exp != null, I18nConstants.SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE, "0", params.size())) { if (rule(errors, "2023-03-01", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), exp != null, I18nConstants.SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE, "0", params.size())) {
try { try {
TypeDetails td = fpe.check(variables, v.getSd().getType(), v.getEd().getPath(), fpe.parse(exp)); TypeDetails td = fpe.check(variables, v.getSd().getUrl(), v.getEd().getPath(), fpe.parse(exp));
if (td.getTypes().size() == 1) { if (td.getTypes().size() == 1) {
type = td.getType(); type = td.getType();
} }
@ -976,7 +993,9 @@ public class StructureMapValidator extends BaseValidator {
private void getElementDefinitionChildrenFromTypes(List<ElementDefinitionSource> result, StructureDefinition sd, ElementDefinition ed, String type, String element) { private void getElementDefinitionChildrenFromTypes(List<ElementDefinitionSource> result, StructureDefinition sd, ElementDefinition ed, String type, String element) {
for (TypeRefComponent td : ed.getType()) { for (TypeRefComponent td : ed.getType()) {
if (type == null | td.getWorkingCode().equals(type)) { String tn = td.getWorkingCode();
StructureDefinition sdt = context.fetchTypeDefinition(tn);
if (type == null || tn.equals(type) || (sdt != null && sdt.getType().equals(type))) {
StructureDefinition tsd = context.fetchTypeDefinition(td.getWorkingCode()); StructureDefinition tsd = context.fetchTypeDefinition(td.getWorkingCode());
if (tsd != null) { if (tsd != null) {
for (ElementDefinition t : tsd.getSnapshot().getElement()) { for (ElementDefinition t : tsd.getSnapshot().getElement()) {
@ -1027,20 +1046,22 @@ public class StructureMapValidator extends BaseValidator {
} }
} }
} else { } else {
StructureMapGroupComponent grp = resolveGroup(name, src); ResolvedGroup grp = resolveGroup(name, src);
if (rule(errors, "2023-03-01", IssueType.NOTFOUND, dependent.line(), dependent.col(), stack.getLiteralPath(), grp != null, I18nConstants.SM_RULEGROUP_NOT_FOUND, name)) { if (rule(errors, "2023-03-01", IssueType.NOTFOUND, dependent.line(), dependent.col(), stack.getLiteralPath(), grp != null, I18nConstants.SM_RULEGROUP_NOT_FOUND, name)) {
List<Element> params = dependent.getChildren("parameter"); List<Element> params = dependent.getChildren("parameter");
if (rule(errors, "2023-03-01", IssueType.INVALID, dependent.line(), dependent.col(), stack.getLiteralPath(), params.size() == grp.getInput().size(), I18nConstants.SM_RULEGROUP_NOT_FOUND, params.size(), grp.getInput().size())) { if (rule(errors, "2023-03-01", IssueType.INVALID, dependent.line(), dependent.col(), stack.getLiteralPath(), params.size() == grp.getTargetGroup().getInput().size(), I18nConstants.SM_RULEGROUP_NOT_FOUND, params.size(), grp.getTargetGroup().getInput().size())) {
VariableSet lvars = new VariableSet(); VariableSet lvars = new VariableSet();
int cc = 0; int cc = 0;
for (Element param : params) { for (Element param : params) {
NodeStack pstack = stack.push(param, cc, null, null); NodeStack pstack = stack.push(param, cc, null, null);
StructureMapGroupInputComponent input = grp.getInput().get(cc); StructureMapGroupInputComponent input = grp.getTargetGroup().getInput().get(cc);
String iType = resolveType(grp, input, src);
String pname = input.getName(); String pname = input.getName();
VariableDefn v = getParameter(errors, param, pstack, variables, input.getMode()); VariableDefn v = getParameter(errors, param, pstack, variables, input.getMode());
if (v != null) { if (v != null) {
if (rule(errors, "2023-03-01", IssueType.INVALID, param.line(), param.col(), pstack.getLiteralPath(), v.mode.equals(input.getMode().toCode()), I18nConstants.SM_DEPENDENT_PARAM_MODE_MISMATCH, param.getChildValue("name"), v.mode, input.getMode().toCode()) && if (rule(errors, "2023-03-01", IssueType.INVALID, param.line(), param.col(), pstack.getLiteralPath(), v.mode.equals(input.getMode().toCode()), I18nConstants.SM_DEPENDENT_PARAM_MODE_MISMATCH, param.getChildValue("name"), v.mode, input.getMode().toCode(), grp.getTargetGroup().getName()) &&
rule(errors, "2023-03-01", IssueType.INVALID, param.line(), param.col(), pstack.getLiteralPath(), typesMatch(v, input.getType()), I18nConstants.SM_DEPENDENT_PARAM_TYPE_MISMATCH, param.getChildValue("name"), v, input.getType())) { rule(errors, "2023-03-01", IssueType.INVALID, param.line(), param.col(), pstack.getLiteralPath(), typesMatch(v, iType), I18nConstants.SM_DEPENDENT_PARAM_TYPE_MISMATCH,
pname, v.summary(), input.getType(), grp.getTargetGroup().getName(), input.getType(), grp.getTargetMap() == null ? "$this" : grp.getTargetMap().getVersionedUrl())) {
lvars.add(pname, v); lvars.add(pname, v);
} else { } else {
ok = false; ok = false;
@ -1050,11 +1071,11 @@ public class StructureMapValidator extends BaseValidator {
} }
cc++; cc++;
} }
if (ok && grp.hasUserData("element.source")) { if (ok && grp.getTargetGroup().hasUserData("element.source")) {
Element g = (Element) grp.getUserData("element.source"); Element g = (Element) grp.getTargetGroup().getUserData("element.source");
if (g.hasUserData("structuremap.parameters")) { if (g.hasUserData("structuremap.parameters")) {
VariableSet pvars = (VariableSet) g.getUserData("structuremap.parameters"); VariableSet pvars = (VariableSet) g.getUserData("structuremap.parameters");
rule(errors, "2023-03-01", IssueType.INVALID, dependent.line(), dependent.col(), stack.getLiteralPath(), pvars.matches(lvars), I18nConstants.SM_DEPENDENT_PARAM_TYPE_MISMATCH_DUPLICATE, grp.getName(), pvars.summary(), lvars.summary()); rule(errors, "2023-03-01", IssueType.INVALID, dependent.line(), dependent.col(), stack.getLiteralPath(), pvars.matches(lvars), I18nConstants.SM_DEPENDENT_PARAM_TYPE_MISMATCH_DUPLICATE, grp.getTargetGroup().getName(), pvars.summary(), lvars.summary());
} else { } else {
g.setUserData("structuremap.parameters", lvars); g.setUserData("structuremap.parameters", lvars);
} }
@ -1067,6 +1088,25 @@ public class StructureMapValidator extends BaseValidator {
return ok; return ok;
} }
private String resolveType(ResolvedGroup grp, StructureMapGroupInputComponent input, Element map) {
if (grp.getTargetMap() == null) {
List<Element> structures = map.getChildrenByName("structure");
for (Element structure : structures) {
String alias = structure.getChildValue("alias");
if (alias != null && alias.equals(input.getType())) {
return structure.getChildValue("url");
}
}
} else {
for (StructureMapStructureComponent struc : grp.getTargetMap().getStructure()) {
if (struc.hasAlias() && struc.getAlias().equals(input.getType())) {
return struc.getUrl();
}
}
}
return input.getType();
}
private StructureMapGroupComponent findDefaultGroup(Element src, String srcType, String tgtType) { private StructureMapGroupComponent findDefaultGroup(Element src, String srcType, String tgtType) {
List<Element> groups = src.getChildrenByName("group"); List<Element> groups = src.getChildrenByName("group");
for (Element group : groups) { for (Element group : groups) {
@ -1126,9 +1166,16 @@ public class StructureMapValidator extends BaseValidator {
} }
private boolean typesMatch(VariableDefn v, String type) { private boolean typesMatch(VariableDefn v, String type) {
if (type == null) { if (type == null || !v.hasTypeInfo()) {
return true;
} else if (v.getSd().getUrl().equals(type) || v.getSd().getType().equals(type)) {
return true; return true;
} else { } else {
for (TypeRefComponent tr : v.getEd().getType()) {
if (type.equals(tr.getWorkingCode()) || type.equals("http://hl7.org/fhir/StructureDefinition/"+tr.getWorkingCode())) {
return true;
}
}
return false; return false;
} }
} }

View File

@ -19,7 +19,7 @@
<properties> <properties>
<hapi_fhir_version>6.2.1</hapi_fhir_version> <hapi_fhir_version>6.2.1</hapi_fhir_version>
<validator_test_case_version>1.2.16</validator_test_case_version> <validator_test_case_version>1.2.17-SNAPSHOT</validator_test_case_version>
<junit_jupiter_version>5.7.1</junit_jupiter_version> <junit_jupiter_version>5.7.1</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version> <junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>
<maven_surefire_version>3.0.0-M5</maven_surefire_version> <maven_surefire_version>3.0.0-M5</maven_surefire_version>