Validation of experimental property

This commit is contained in:
Grahame Grieve 2024-09-17 16:16:09 +08:00
parent 0d8c5942e7
commit 11efed6635
9 changed files with 62 additions and 11 deletions

View File

@ -1108,4 +1108,7 @@ public class I18nConstants {
public static final String SM_TARGET_TYPE_UNKNOWN = "SM_TARGET_TYPE_UNKNOWN";
public static final String XHTML_XHTML_ATTRIBUTE_XML_SPACE = "XHTML_XHTML_ATTRIBUTE_XML_SPACE";
public static final String VALIDATION_HL7_PUBLISHER_MULTIPLE_WGS = "VALIDATION_HL7_PUBLISHER_MULTIPLE_WGS";
public static final String SD_BASE_EXPERIMENTAL = "SD_BASE_EXPERIMENTAL";
public static final String SD_ED_EXPERIMENTAL_BINDING = "SD_ED_EXPERIMENTAL_BINDING";
public static final String VALIDATION_NO_EXPERIMENTAL_CONTENT = "VALIDATION_NO_EXPERIMENTAL_CONTENT";
}

View File

@ -1140,4 +1140,6 @@ XHTML_XHTML_ATTRIBUTE_XML_SPACE = The attribute 'xml:space' is legal but has a f
VALIDATION_HL7_PUBLISHER_MULTIPLE_WGS = This resource has more than workgroup extension (http://hl7.org/fhir/StructureDefinition/structuredefinition-wg)
NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG = Wrong Display Name ''{0}'' for {1}#{2}. There are no valid display names found for language(s) ''{3}''. Default display is ''{4}''
NO_VALID_DISPLAY_AT_ALL = Cannot validate display Name ''{0}'' for {1}#{2}: No displays are known
SD_BASE_EXPERIMENTAL = The definition builds on ''{0}'' which is experimental, but this definition is not labeled as experimental
SD_ED_EXPERIMENTAL_BINDING = The definition for the element ''{0}'' binds to the value set ''{1}'' which is experimental, but this structure is not labeled as experimental
VALIDATION_NO_EXPERIMENTAL_CONTENT = Experimental content is not allowed in this context

View File

@ -227,6 +227,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
@Getter @Setter private boolean checkIPSCodes;
@Getter @Setter private BestPracticeWarningLevel bestPracticeLevel;
@Getter @Setter private boolean unknownCodeSystemsCauseErrors;
@Getter @Setter private boolean noExperimentalContent;
@Getter @Setter private Locale locale;
@Getter @Setter private List<ImplementationGuide> igs = new ArrayList<>();
@Getter @Setter private List<String> extensionDomains = new ArrayList<>();
@ -909,6 +910,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
validator.setPolicyAdvisor(policyAdvisor);
}
validator.setUnknownCodeSystemsCauseErrors(unknownCodeSystemsCauseErrors);
validator.setNoExperimentalContent(noExperimentalContent);
return validator;
}

View File

@ -165,6 +165,9 @@ public class CliContext {
@JsonProperty("unknownCodeSystemsCauseErrors")
private boolean unknownCodeSystemsCauseErrors;
@JsonProperty("noExperimentalContent")
private boolean noExperimentalContent;
@JsonProperty("baseEngine")
public String getBaseEngine() {
@ -836,6 +839,7 @@ public class CliContext {
Objects.equals(bestPracticeLevel, that.bestPracticeLevel) &&
Objects.equals(watchScanDelay, that.watchScanDelay) &&
Objects.equals(unknownCodeSystemsCauseErrors, that.unknownCodeSystemsCauseErrors) &&
Objects.equals(noExperimentalContent, that.noExperimentalContent) &&
Objects.equals(watchSettleTime, that.watchSettleTime) ;
}
@ -844,7 +848,7 @@ public class CliContext {
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
}
@Override
@ -904,6 +908,7 @@ public class CliContext {
", watchSettleTime=" + watchSettleTime +
", watchScanDelay=" + watchScanDelay +
", unknownCodeSystemsCauseErrors=" + unknownCodeSystemsCauseErrors +
", noExperimentalContent=" + noExperimentalContent +
'}';
}
@ -973,5 +978,16 @@ public class CliContext {
this.unknownCodeSystemsCauseErrors = unknownCodeSystemsCauseErrors;
}
@JsonProperty("noExperimentalContent")
public boolean isNoExperimentalContent() {
return noExperimentalContent;
}
@JsonProperty("noExperimentalContent")
public void setNoExperimentalContent(boolean noExperimentalContent) {
this.noExperimentalContent = noExperimentalContent;
}
}

View File

@ -582,6 +582,7 @@ public class ValidationService {
validationEngine.getBundleValidationRules().addAll(cliContext.getBundleValidationRules());
validationEngine.setJurisdiction(CodeSystemUtilities.readCoding(cliContext.getJurisdiction()));
validationEngine.setUnknownCodeSystemsCauseErrors(cliContext.isUnknownCodeSystemsCauseErrors());
validationEngine.setNoExperimentalContent(cliContext.isNoExperimentalContent());
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
validationEngine.prepare(); // generate any missing snapshots
System.out.println(" go (" + timeTracker.milestone() + ")");

View File

@ -94,7 +94,8 @@ public class Params {
public static final String DISABLE_DEFAULT_RESOURCE_FETCHER = "-disable-default-resource-fetcher";
public static final String CHECK_IPS_CODES = "-check-ips-codes";
public static final String BEST_PRACTICE = "-best-practice";
public static final String UNKNOWN_CODESYSTEMS_CAUSE_ERROR = "-unknown-codesystems-cause-errors";
public static final String UNKNOWN_CODESYSTEMS_CAUSE_ERROR = "-unknown-codesystems-cause-errors";
public static final String NO_EXPERIMENTAL_CONTENT = "-no-experimental-content";
public static final String RUN_TESTS = "-run-tests";
@ -321,6 +322,8 @@ public class Params {
cliContext.setForPublication(true);
} else if (args[i].equals(UNKNOWN_CODESYSTEMS_CAUSE_ERROR)) {
cliContext.setUnknownCodeSystemsCauseErrors(true);
} else if (args[i].equals(NO_EXPERIMENTAL_CONTENT)) {
cliContext.setNoExperimentalContent(true);
} else if (args[i].equals(VERBOSE)) {
cliContext.setCrumbTrails(true);
} else if (args[i].equals(ALLOW_EXAMPLE_URLS)) {

View File

@ -599,6 +599,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private IDigitalSignatureServices signatureServices;
private ContextUtilities cu;
private boolean unknownCodeSystemsCauseErrors;
private boolean noExperimentalContent;
public InstanceValidator(@Nonnull IWorkerContext theContext, @Nonnull IEvaluationContext hostServices, @Nonnull XVerExtensionManager xverManager) {
super(theContext, xverManager, false);
@ -5718,6 +5719,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
hint(errors, "2023-08-14", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), statusCodesDeeplyConsistent(status, standardsStatus), I18nConstants.VALIDATION_VAL_STATUS_INCONSISTENT_HINT, status, standardsStatus);
}
}
if (noExperimentalContent) {
String exp = element.getNamedChildValue("experimental");
ok = rule(errors, "2024-09-17", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), !"true".equals(exp), I18nConstants.VALIDATION_NO_EXPERIMENTAL_CONTENT) && ok;
}
if (isHL7Core(element) && !isExample()) {
ok = checkPublisherConsistency(valContext, errors, element, stack, contained) && ok;
@ -7839,6 +7844,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public void setUnknownCodeSystemsCauseErrors(boolean unknownCodeSystemsCauseErrors) {
this.unknownCodeSystemsCauseErrors = unknownCodeSystemsCauseErrors;
}
public boolean isNoExperimentalContent() {
return noExperimentalContent;
}
public void setNoExperimentalContent(boolean noExperimentalContent) {
this.noExperimentalContent = noExperimentalContent;
}
}

View File

@ -85,6 +85,7 @@ public class StructureDefinitionValidator extends BaseValidator {
List<ElementDefinition> snapshot = sd.getSnapshot().getElement();
sd.setSnapshot(null);
typeName = sd.getTypeName();
boolean experimental = "true".equals(src.getNamedChildValue("experimental", false));
StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
if (warning(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) {
if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasDerivation(), I18nConstants.SD_MUST_HAVE_DERIVATION, sd.getUrl())) {
@ -129,6 +130,7 @@ public class StructureDefinitionValidator extends BaseValidator {
ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), base.getKindElement().primitiveValue().equals(src.getChildValue("kind")),
I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")) && ok;
}
warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), !base.getExperimental() || experimental, I18nConstants.SD_BASE_EXPERIMENTAL, sd.getBaseDefinition());
}
List<Element> differentials = src.getChildrenByName("differential");
@ -136,10 +138,10 @@ public class StructureDefinitionValidator extends BaseValidator {
boolean logical = "logical".equals(src.getNamedChildValue("kind", false));
boolean constraint = "constraint".equals(src.getNamedChildValue("derivation", false));
for (Element differential : differentials) {
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint, src.getNamedChildValue("type", false), src.getNamedChildValue("url", false), src.getNamedChildValue("type", false), base) && ok;
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint, src.getNamedChildValue("type", false), src.getNamedChildValue("url", false), src.getNamedChildValue("type", false), base, experimental) && ok;
}
for (Element snapshotE : snapshots) {
ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint, src.getNamedChildValue("type", false), src.getNamedChildValue("url", false), src.getNamedChildValue("type", false), base) && ok;
ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint, src.getNamedChildValue("type", false), src.getNamedChildValue("url", false), src.getNamedChildValue("type", false), base, experimental) && ok;
}
// obligation profile support
@ -189,6 +191,7 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
} catch (Exception e) {
e.printStackTrace();
rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage());
ok = false;
}
@ -409,19 +412,19 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl, String profileType, StructureDefinition base) {
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl, String profileType, StructureDefinition base, boolean experimental) {
Map<String, String> invariantMap = new HashMap<>();
boolean ok = true;
List<Element> elements = elementList.getChildrenByName("element");
int cc = 0;
for (Element element : elements) {
ok = validateElementDefinition(errors, elements, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint, invariantMap, rootPath, profileUrl, profileType, base) && ok;
ok = validateElementDefinition(errors, elements, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint, invariantMap, rootPath, profileUrl, profileType, base, experimental) && ok;
cc++;
}
return ok;
}
private boolean validateElementDefinition(List<ValidationMessage> errors, List<Element> elements, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map<String, String> invariantMap, String rootPath, String profileUrl, String profileType, StructureDefinition base) {
private boolean validateElementDefinition(List<ValidationMessage> errors, List<Element> elements, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map<String, String> invariantMap, String rootPath, String profileUrl, String profileType, StructureDefinition base, boolean experimental) {
boolean ok = true;
boolean typeMustSupport = false;
String path = element.getNamedChildValue("path", false);
@ -522,7 +525,7 @@ public class StructureDefinitionValidator extends BaseValidator {
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);
ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path) && ok;
ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path, experimental) && ok;
} else {
// this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back
// String bt = boundType(typeCodes);
@ -986,7 +989,7 @@ public class StructureDefinitionValidator extends BaseValidator {
return null;
}
private boolean validateBinding(List<ValidationMessage> errors, Element binding, NodeStack stack, Set<String> typeCodes, boolean snapshot, String path) {
private boolean validateBinding(List<ValidationMessage> errors, Element binding, NodeStack stack, Set<String> typeCodes, boolean snapshot, String path, boolean experimental) {
boolean ok = true;
if (bindableType(typeCodes) == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot, I18nConstants.SD_ED_BIND_NO_BINDABLE, path, typeCodes.toString()) && ok;
@ -1006,7 +1009,12 @@ public class StructureDefinitionValidator extends BaseValidator {
if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
if (vs != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType()) && ok;
if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType())) {
ValueSet vsr = (ValueSet) vs;
warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), !vsr.getExperimental() || experimental, I18nConstants.SD_ED_EXPERIMENTAL_BINDING, path, ref);
} else {
ok = false;
}
}
}
}

View File

@ -362,6 +362,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (content.has("security-checks")) {
val.setSecurityChecks(content.get("security-checks").getAsBoolean());
}
if (content.has("no-experimental-content")) {
val.setNoExperimentalContent(content.get("no-experimental-content").getAsBoolean());
}
if (content.has("noHtmlInMarkdown")) {
val.setHtmlInMarkdownCheck(HtmlInMarkdownCheck.ERROR);
}