Add IG dependency validator

This commit is contained in:
Grahame Grieve 2024-06-13 12:14:03 +10:00
parent a9f0b70e30
commit e731c0c059
4 changed files with 141 additions and 0 deletions

View File

@ -1080,4 +1080,15 @@ public class I18nConstants {
public static final String SD_TYPE_PARAMETER_INVALID_REF = "SD_TYPE_PARAMETER_INVALID_REF";
public static final String SD_TYPE_PARAM_NOT_SPECIFIED = "SD_TYPE_PARAM_NOT_SPECIFIED";
public static final String SD_TYPE_PARAMETER_ABSTRACT_WARNING = "SD_TYPE_PARAMETER_ABSTRACT_WARNING";
public static final String IG_DEPENDENCY_DIRECT = "IG_DEPENDENCY_DIRECT";
public static final String IG_DEPENDENCY_INVALID_PACKAGEID = "IG_DEPENDENCY_INVALID_PACKAGEID";
public static final String IG_DEPENDENCY_CLASH_PACKAGEID = "IG_DEPENDENCY_CLASH_PACKAGEID";
public static final String IG_DEPENDENCY_CLASH_CANONICAL = "IG_DEPENDENCY_CLASH_CANONICAL";
public static final String IG_DEPENDENCY_NO_PACKAGE = "IG_DEPENDENCY_NO_PACKAGE";
public static final String IG_DEPENDENCY_NO_VERSION = "IG_DEPENDENCY_NO_VERSION";
public static final String IG_DEPENDENCY_INVALID_PACKAGE_VERSION = "IG_DEPENDENCY_INVALID_PACKAGE_VERSION";
public static final String IG_DEPENDENCY_VERSION_ERROR = "IG_DEPENDENCY_VERSION_ERROR";
public static final String IG_DEPENDENCY_VERSION_WARNING = "IG_DEPENDENCY_VERSION_WARNING";
public static final String IG_DEPENDENCY_EXCEPTION = "IG_DEPENDENCY_EXCEPTION";
public static final String IG_DEPENDENCY_PACKAGE_UNKNOWN = "IG_DEPENDENCY_PACKAGE_UNKNOWN";
}

View File

@ -1108,3 +1108,14 @@ SD_TYPE_PARAMETER_INVALID = The type definition ''{2}'' has a type parameter ''{
SD_TYPE_PARAMETER_INVALID_REF = The type ''{0}'' is a reference to ''{1}'' which has a type parameter ''{2}'' with a base type of {3} but the type parameter provided is ''{4}'' which is not the right type
SD_TYPE_PARAM_NOT_SPECIFIED = The type ''{0}'' at {3} is a reference to ''{1}'' which needs a type parameter ''{2}'' but a type parameter is not provided for ''{2}''
SD_TYPE_PARAMETER_ABSTRACT_WARNING = The type ''{0}'' at {3} refers to the abstract type ''{1}'' but the context is not an abstract type - this is usually an error
IG_DEPENDENCY_DIRECT = The URL should refer directly to the ImplementationGuide resource (e.g. include ''/ImplementationGuide/'')
PACKAGE_REGEX = The packageId {0} is not valid
IG_DEPENDENCY_CLASH_PACKAGEID = The canonical URL {0} points to the package {1} which is inconsistent with the stated packageId of {2}
IG_DEPENDENCY_CLASH_CANONICAL = The packageId {0} points to the canonical {1} which is inconsistent with the stated canonical of {2}
IG_DEPENDENCY_NO_PACKAGE = No NPM package id could be determined so the version consistency can't be checked
IG_DEPENDENCY_NO_VERSION = No version was specified for the package so the version consistency can't be checked
IG_DEPENDENCY_INVALID_PACKAGE_VERSION = The version {0} is not a valid NPM package version
IG_DEPENDENCY_PACKAGE_UNKNOWN = The package {0} could not be found so the version consistency can't be checked
IG_DEPENDENCY_VERSION_ERROR = The ImplementationGuide is based on FHIR version {0} but package {1} is based on FHIR version {2}. Use the package {3} instead
IG_DEPENDENCY_VERSION_WARNING = The ImplementationGuide is based on FHIR version {0} but package {1} is based on FHIR version {2}. In general, this version mismatch should be avoided - some tools will try to make this work with variable degrees of success, but others will not even try
IG_DEPENDENCY_EXCEPTION = Exception checking package version consistency: {0}

View File

@ -219,6 +219,7 @@ import org.hl7.fhir.validation.instance.InstanceValidator.BindingContext;
import org.hl7.fhir.validation.instance.type.BundleValidator;
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.ConceptMapValidator;
import org.hl7.fhir.validation.instance.type.ImplementationGuideValidator;
import org.hl7.fhir.validation.instance.type.MeasureValidator;
import org.hl7.fhir.validation.instance.type.ObservationValidator;
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
@ -5771,6 +5772,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return new StructureMapValidator(this, fpe, profileUtilities).validateStructureMap(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("ValueSet")) {
return new ValueSetValidator(this).validateValueSet(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("ImplementationGuide")) {
return new ImplementationGuideValidator(this.context, xverManager, debug).validateImplementationGuide(valContext, errors, element, stack) && ok;
} else if ("http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/ViewDefinition".equals(element.getProperty().getStructure().getUrl())) {
if (element.getNativeObject() != null && element.getNativeObject() instanceof JsonObject) {
JsonObject json = (JsonObject) element.getNativeObject();

View File

@ -0,0 +1,116 @@
package org.hl7.fhir.validation.instance.type;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DateType;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Questionnaire;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemAnswerOptionComponent;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.TimeType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationContextResourceProxy;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.instance.utils.EnableWhenEvaluator;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
import org.hl7.fhir.validation.instance.utils.EnableWhenEvaluator.QStack;
import ca.uhn.fhir.util.ObjectUtil;
public class ImplementationGuideValidator extends BaseValidator {
public ImplementationGuideValidator(IWorkerContext context, XVerExtensionManager xverManager, boolean debug) {
super(context, xverManager, debug);
}
public boolean validateImplementationGuide(ValidationContext valContext, List<ValidationMessage> errors, Element ig, NodeStack stack) {
boolean ok = true;
String fver = ig.getNamedChildValue("fhirVersion");
List<Element> dependencies = ig.getChildrenByName("dependsOn");
int i = 0;
for (Element dependency : dependencies) {
ok = checkDependency(errors, ig, stack.push(dependency, i, null, null), dependency, fver) && ok;
i++;
}
return ok;
}
private boolean checkDependency(List<ValidationMessage> errors, Element ig, NodeStack stack, Element dependency, String fver) {
boolean ok = true;
String url = dependency.getNamedChildValue("url");
String packageId = dependency.getNamedChildValue("packageId");
String version = dependency.getNamedChildValue("version");
ok = rule(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), url == null || url.contains("/ImplementationGuide/"), I18nConstants.IG_DEPENDENCY_DIRECT, url) && ok;
ok = rule(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), packageId == null || packageId.matches(FilesystemPackageCacheManager.PACKAGE_REGEX), I18nConstants.IG_DEPENDENCY_INVALID_PACKAGEID, packageId) && ok;
try {
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build();
if (url != null && packageId != null) {
String pid = pcm.getPackageId(url);
String canonical = pcm.getPackageUrl(packageId);
ok = rule(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), pid == null || pid.equals(packageId), I18nConstants.IG_DEPENDENCY_CLASH_PACKAGEID, url, pid, packageId) && ok;
ok = rule(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), canonical == null || canonical.equals(url), I18nConstants.IG_DEPENDENCY_CLASH_CANONICAL, packageId, canonical, url) && ok;
}
if (packageId == null && ok) {
packageId = pcm.getPackageId(url);
}
if (ok && warning(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), packageId != null, I18nConstants.IG_DEPENDENCY_NO_PACKAGE) &&
warning(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), version != null, I18nConstants.IG_DEPENDENCY_NO_VERSION)) {
ok = rule(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), (packageId+"#"+version).matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX), I18nConstants.IG_DEPENDENCY_INVALID_PACKAGE_VERSION, version) && ok;
NpmPackage npm = pcm.loadPackage(packageId, version);
if (warning(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), npm != null, I18nConstants.IG_DEPENDENCY_PACKAGE_UNKNOWN, packageId+"#"+version)) {
String pver = npm.fhirVersion();
if (!VersionUtilities.versionsMatch(pver, fver)) {
if ("hl7.fhir.uv.extensions".equals(packageId)) {
ok = rule(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), false, I18nConstants.IG_DEPENDENCY_VERSION_ERROR, fver, packageId+"#"+version, pver,
"hl7.fhir.uv.extensions."+VersionUtilities.getNameForVersion(fver).toLowerCase()) && ok;
} else {
warning(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), false, I18nConstants.IG_DEPENDENCY_VERSION_WARNING, fver, packageId+"#"+version, pver);
}
}
}
}
} catch (Exception e) {
warning(errors, "2024-06-13", IssueType.BUSINESSRULE, dependency.line(), dependency.col(), stack.getLiteralPath(), version != null, I18nConstants.IG_DEPENDENCY_EXCEPTION, e.getMessage());
}
return ok;
}
}