v2 tests, support discriminator by position, and don't check type characteristics for unknown types

This commit is contained in:
Grahame Grieve 2024-05-26 07:56:16 -05:00
parent a8ba174383
commit b766cffdb1
6 changed files with 83 additions and 27 deletions

View File

@ -899,7 +899,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED));
} else if (noTerminologyServer) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,
formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES),
formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, t.getCoding().getCode(), t.getCoding().getSystem()),
TerminologyServiceErrorClass.NOSERVICE));
}
}
@ -1019,7 +1019,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
// if that failed, we try to validate on the server
if (noTerminologyServer) {
return new ValidationResult(IssueSeverity.ERROR,
formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES),
formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, code.getCode(), code.getSystem()),
TerminologyServiceErrorClass.NOSERVICE);
}
String csumm = txCache != null ? txCache.summary(code) : null;

View File

@ -1082,7 +1082,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} else if (unsupportedCodeSystems.contains(codeKey)) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));
} else if (noTerminologyServer) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null));
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, t.getCoding().getCode(), t.getCoding().getSystem()), TerminologyServiceErrorClass.NOSERVICE, null));
}
}
}
@ -1190,7 +1190,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} else if (unsupportedCodeSystems.contains(codeKey)) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));
} else if (noTerminologyServer) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null));
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, t.getCoding().getCode(), t.getCoding().getSystem()), TerminologyServiceErrorClass.NOSERVICE, null));
}
}
}
@ -1346,7 +1346,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
// if that failed, we try to validate on the server
if (noTerminologyServer) {
return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, issues);
return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, code.getCode(), code.getSystem()), TerminologyServiceErrorClass.NOSERVICE, issues);
}
Set<String> systems = findRelevantSystems(code, vs);

View File

@ -963,6 +963,15 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
public void incCount() {
count++;
}
public boolean containsText(List<String> fragements) {
for (String s : fragements) {
if ((getMessage() != null && getMessage().contains(s)) || (getMessageId() != null && getMessageId().contains(s))) {
return true;
}
}
return false;
}
}

View File

@ -4960,7 +4960,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* @throws IOException
* @throws FHIRException
*/
private boolean sliceMatches(ValidationContext valContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException {
private boolean sliceMatches(ValidationContext valContext, Element element, String path, ElementDefinition slicer, List<ElementDefinition> slicerSlices, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException {
if (!slicer.getSlicing().hasDiscriminator())
return false; // cannot validate in this case
@ -5034,6 +5034,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0, discriminator, ed.getId()));
}
} else if (s.getType() == DiscriminatorType.POSITION) {
// we don't evaluate this one using FHIRPath, and it can't share
if (slicer.getSlicing().getDiscriminator().size() != 1) {
throw new DefinitionException(context.formatMessagePlural(slicer.getSlicing().getDiscriminator().size(), I18nConstants.Could_not_match_discriminator_for_slice_in_profile, discriminators, ed.getId(), profile.getVersionedUrl(), discriminators));
} else {
int offset = 0;
for (ElementDefinition ts : slicerSlices) {
if (ts == ed) {
break;
} else if (!ts.getMax().equals(Integer.toString(ts.getMin()))) {
throw new DefinitionException(context.formatMessagePlural(slicer.getSlicing().getDiscriminator().size(), I18nConstants.Could_not_match_discriminator_for_slice_in_profile, discriminators, ed.getId(), profile.getVersionedUrl(), discriminators));
} else {
offset = offset + ts.getMin();
}
}
int maxPos = (ed.getMax().equals("*") ? Integer.MAX_VALUE : offset + Integer.parseInt(ed.getMax()));
int position = path.endsWith("]") ? Integer.parseInt(path.substring(path.lastIndexOf("[")+1).replace("]", "")) : 0;
return position >= offset && position < maxPos;
}
} else if (criteriaElement.hasFixed()) {
buildFixedExpression(ed, expression, discriminator, criteriaElement);
} else if (criteriaElement.hasPattern()) {
@ -6808,6 +6827,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// 2. assign children to a definition
// for each definition, for each child, check whether it belongs in the slice
ElementDefinition slicer = null;
List<ElementDefinition> slicerSlices = null;
boolean unsupportedSlicing = false;
List<String> problematicPaths = new ArrayList<String>();
String slicingPath = null;
@ -6835,14 +6856,22 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
slicer = ed;
process = false;
sliceOffset = i;
} else if (slicer != null && !slicer.getPath().equals(ed.getPath()))
slicerSlices = new ArrayList<>();
for (int j = i+1; j < childDefinitions.getList().size(); j++) {
if (ed.getPath().equals(childDefinitions.getList().get(j).getPath())) {
slicerSlices.add(childDefinitions.getList().get(j));
}
}
} else if (slicer != null && !slicer.getPath().equals(ed.getPath())) {
slicer = null;
slicerSlices = null;
}
for (ElementInfo ei : children) {
if (ei.sliceInfo == null) {
ei.sliceInfo = new ArrayList<>();
}
unsupportedSlicing = matchSlice(valContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei, bh);
unsupportedSlicing = matchSlice(valContext, errors, ei.sliceInfo, profile, stack, slicer, slicerSlices, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei, bh);
}
}
int last = -1;
@ -6928,7 +6957,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
public boolean matchSlice(ValidationContext valContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack,
ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed,
ElementDefinition slicer, List<ElementDefinition> slicerSlices, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed,
boolean childUnsupportedSlicing, ElementInfo ei, BooleanHolder bh) {
boolean match = false;
if (slicer == null || slicer == ed) {
@ -6937,7 +6966,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (nameMatches(ei.getName(), tail(ed.getPath())))
try {
// System.out.println("match slices for "+stack.getLiteralPath()+": "+slicer.getId()+" = "+slicingSummary(slicer.getSlicing()));
match = sliceMatches(valContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile);
match = sliceMatches(valContext, ei.getElement(), ei.getPath(), slicer, slicerSlices, ed, profile, errors, sliceInfo, stack, profile);
if (match) {
ei.slice = slicer;

View File

@ -411,9 +411,11 @@ public class StructureDefinitionValidator extends BaseValidator {
List<Element> types = element.getChildrenByName("type");
Set<String> typeCodes = new HashSet<>();
Set<String> characteristics = new HashSet<>();
boolean characteristicsValid = false;
if (!path.contains(".")) {
typeCodes.add(path); // root is type
addCharacteristics(characteristics, path);
characteristicsValid = true;
}
if (!snapshot && (element.hasChild("fixed") || element.hasChild("pattern")) && base != null) {
@ -463,17 +465,20 @@ public class StructureDefinitionValidator extends BaseValidator {
typeCodes.add(tc);
Set<String> tcharacteristics = new HashSet<>();
StructureDefinition tsd = context.fetchTypeDefinition(tc);
if (tsd != null && tsd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
for (Extension ext : tsd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
tcharacteristics.add(ext.getValue().primitiveValue());
if (tsd != null) {
characteristicsValid = true;
if (tsd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
for (Extension ext : tsd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
tcharacteristics.add(ext.getValue().primitiveValue());
}
} else {
// nothing specified, so infer from known types
addCharacteristics(tcharacteristics, tc);
}
characteristics.addAll(tcharacteristics);
if (type.hasChildren("targetProfile")) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), tcharacteristics.contains("has-target") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "targetProfile", tc) && ok;
}
} else {
// nothing specified, so infer from known types
addCharacteristics(tcharacteristics, tc);
}
characteristics.addAll(tcharacteristics);
if (type.hasChildren("targetProfile")) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), tcharacteristics.contains("has-target") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "targetProfile", tc) && ok;
}
// check the stated profile - must be a constraint on the type
if (snapshot || sd != null) {
@ -489,7 +494,7 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
if (element.hasChild("binding", false)) {
if (!typeCodes.isEmpty()) {
if (!typeCodes.isEmpty() && characteristicsValid) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("can-bind") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Binding", typeCodes) && ok;
}
Element binding = element.getNamedChild("binding", false);
@ -499,7 +504,7 @@ public class StructureDefinitionValidator extends BaseValidator {
// String bt = boundType(typeCodes);
// hint(errors, UNKNOWN_DATE_TIME, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path", false), bt);
}
if (!typeCodes.isEmpty()) {
if (!typeCodes.isEmpty() && characteristicsValid) {
if (element.hasChild("maxLength", false)) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("has-length") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "MaxLength", typeCodes) && ok;
}

View File

@ -353,13 +353,20 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (content.has("noHtmlInMarkdown")) {
val.setHtmlInMarkdownCheck(HtmlInMarkdownCheck.ERROR);
}
List<String> suppress = new ArrayList<>();
if (content.has("suppress")) {
for (JsonElement c : content.getAsJsonArray("suppress")) {
suppress.add(c.getAsString());
}
}
val.setSignatureServices(this);
if (content.has("logical")==false) {
val.setAssumeValidRestReferences(content.has("assumeValidRestReferences") ? content.get("assumeValidRestReferences").getAsBoolean() : false);
logOutput(String.format("Start Validating (%d to set up)", (System.nanoTime() - setup) / 1000000));
val.validate(null, errors, new ByteArrayInputStream(testCaseContent), fmt);
logOutput(val.reportTimes());
checkOutcomes(errors, content, null, name);
checkOutcomes(errors, content, null, name, suppress);
}
if (content.has("profile")) {
System.out.print("** Profile: ");
@ -388,6 +395,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
}
}
if (content.has("suppress")) {
for (JsonElement c : content.getAsJsonArray("suppress")) {
suppress.add(c.getAsString());
}
}
String filename = profile.get("source").getAsString();
if (Utilities.isAbsoluteUrl(filename)) {
sd = val.getContext().fetchResource(StructureDefinition.class, filename);
@ -403,7 +415,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
List<ValidationMessage> errorsProfile = new ArrayList<ValidationMessage>();
val.validate(null, errorsProfile, new ByteArrayInputStream(testCaseContent), fmt, asSdList(sd));
logOutput(val.reportTimes());
checkOutcomes(errorsProfile, profile, filename, name);
checkOutcomes(errorsProfile, profile, filename, name, suppress);
}
if (content.has("logical")) {
System.out.print("** Logical: ");
@ -445,7 +457,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
Assert.assertTrue(fp.evaluateToBoolean(null, le, le, le, fp.parse(exp)));
}
}
checkOutcomes(errorsLogical, logical, "logical", name);
checkOutcomes(errorsLogical, logical, "logical", name, suppress);
}
logger.verifyHasNoRequests();
}
@ -457,7 +469,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
private ValidationEngine buildVersionEngine(String ver, String txLog) throws Exception {
String server = FhirSettings.getTxFhirDevelopment();
String server = FhirSettings.getTxFhirLocal();
switch (ver) {
case "1.0": return TestUtilities.getValidationEngine("hl7.fhir.r2.core#1.0.2", server, txLog, FhirPublication.DSTU2, true, "1.0.2");
case "1.4": return TestUtilities.getValidationEngine("hl7.fhir.r2b.core#1.4.0", server, txLog, FhirPublication.DSTU2016May, true, "1.4.0");
@ -533,7 +545,8 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
}
private void checkOutcomes(List<ValidationMessage> errors, JsonObject focus, String profile, String name) throws IOException {
private void checkOutcomes(List<ValidationMessage> errors, JsonObject focus, String profile, String name, List<String> suppress) throws IOException {
errors.removeIf(vm -> vm.containsText(suppress));
JsonObject java = focus.getAsJsonObject("java");
OperationOutcome goal = java.has("outcome") ? (OperationOutcome) new JsonParser().parse(java.getAsJsonObject("outcome")) : new OperationOutcome();
OperationOutcome actual = OperationOutcomeUtilities.createOutcomeSimple(errors);