Fix validation issues for StructureDefinitions (#396)
This commit is contained in:
parent
118c03590f
commit
7de14f172e
|
@ -109,7 +109,7 @@ public interface IResourceValidator {
|
|||
|
||||
Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException;
|
||||
ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url);
|
||||
boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException;
|
||||
boolean resolveURL(Object appContext, String path, String url, String type) throws IOException, FHIRException;
|
||||
|
||||
byte[] fetchRaw(String url) throws MalformedURLException, IOException; // for attachment checking
|
||||
|
||||
|
|
|
@ -364,6 +364,7 @@ public class VersionUtilities {
|
|||
res.add("TestScript");
|
||||
}
|
||||
if (isR3Ver(version)) {
|
||||
res.add("CodeSystem");
|
||||
res.add("CapabilityStatement");
|
||||
res.add("StructureDefinition");
|
||||
res.add("ImplementationGuide");
|
||||
|
@ -388,6 +389,7 @@ public class VersionUtilities {
|
|||
}
|
||||
if (isR4Ver(version)) {
|
||||
|
||||
res.add("CodeSystem");
|
||||
res.add("ActivityDefinition");
|
||||
res.add("CapabilityStatement");
|
||||
res.add("ChargeItemDefinition");
|
||||
|
@ -419,7 +421,7 @@ public class VersionUtilities {
|
|||
res.add("ValueSet");
|
||||
}
|
||||
|
||||
if (isR5Ver(version)) {
|
||||
if (isR5Ver(version) || "current".equals(version)) {
|
||||
|
||||
res.add("ActivityDefinition");
|
||||
res.add("CapabilityStatement");
|
||||
|
|
|
@ -350,6 +350,7 @@ public class I18nConstants {
|
|||
public static final String SD_ED_TYPE_PROFILE_UNKNOWN = "SD_ED_TYPE_PROFILE_UNKNOWN";
|
||||
public static final String SD_ED_TYPE_PROFILE_NOTYPE = "SD_ED_TYPE_PROFILE_NOTYPE";
|
||||
public static final String SD_ED_TYPE_PROFILE_WRONG = "SD_ED_TYPE_PROFILE_WRONG";
|
||||
public static final String SD_ED_TYPE_PROFILE_WRONG_TARGET = "SD_ED_TYPE_PROFILE_WRONG_TARGET";
|
||||
public static final String SD_ED_TYPE_NO_TARGET_PROFILE = "SD_ED_TYPE_NO_TARGET_PROFILE";
|
||||
public static final String SEARCHPARAMETER_BASE_WRONG = "SEARCHPARAMETER_BASE_WRONG";
|
||||
public static final String SEARCHPARAMETER_EXP_WRONG = "SEARCHPARAMETER_EXP_WRONG";
|
||||
|
@ -593,6 +594,11 @@ public class I18nConstants {
|
|||
public static final String XHTML_URL_EMPTY = "XHTML_URL_EMPTY";
|
||||
public static final String XHTML_URL_INVALID = "XHTML_URL_INVALID";
|
||||
public static final String XHTML_URL_INVALID_CHARS = "XHTML_URL_INVALID_CHARS";
|
||||
public static final String XHTML_URL_DATA_NO_DATA = "XHTML_URL_DATA_NO_DATA";
|
||||
public static final String XHTML_URL_DATA_DATA_INVALID_COMMA = "XHTML_URL_DATA_DATA_INVALID_COMMA";
|
||||
public static final String XHTML_URL_DATA_DATA_INVALID = "XHTML_URL_DATA_DATA_INVALID";
|
||||
public static final String XHTML_URL_DATA_MIMETYPE = "XHTML_URL_DATA_MIMETYPE";
|
||||
|
||||
public static final String XHTML_XHTML_ATTRIBUTE_ILLEGAL = "XHTML_XHTML_Attribute_Illegal";
|
||||
public static final String XHTML_XHTML_DOCTYPE_ILLEGAL = "XHTML_XHTML_DOCTYPE_ILLEGAL";
|
||||
public static final String XHTML_XHTML_ELEMENT_ILLEGAL = "XHTML_XHTML_Element_Illegal";
|
||||
|
|
|
@ -443,7 +443,7 @@ documentmsg = (document)
|
|||
xml_attr_value_invalid = The XML Attribute {0} has an illegal character
|
||||
xml_encoding_invalid = The XML encoding is invalid (must be UTF-8)
|
||||
xml_stated_encoding_invalid = The XML encoding stated in the header is invalid (must be ''UTF-8'' if stated)
|
||||
XHTML_URL_INVALID = The URL {0} is not valid ({1})
|
||||
XHTML_URL_INVALID = The URL is not valid because ''({1})'' : {0}
|
||||
MEASURE_MR_GRP_NO_CODE = Group should have a code that matches the group definition in the measure
|
||||
MEASURE_MR_GRP_UNK_CODE = The code for this group has no match in the measure definition
|
||||
MEASURE_MR_GRP_DUPL_CODE = The code for this group is duplicated with another group
|
||||
|
@ -619,8 +619,12 @@ Unable_to_connect_to_terminology_server = Unable to connect to terminology serve
|
|||
SD_ED_TYPE_PROFILE_UNKNOWN = Unable to resolve profile {0}
|
||||
SD_ED_TYPE_PROFILE_NOTYPE = Found profile {0}, but unable to determine the type it applies it
|
||||
SD_ED_TYPE_PROFILE_WRONG = Profile {0} is for type {1}, but the {3} element has type {2}
|
||||
SD_ED_TYPE_PROFILE_WRONG_TARGET = Profile {0} is for type {1}, which is not a {4} (which is required because the {3} element has type {2})
|
||||
SD_ED_TYPE_NO_TARGET_PROFILE = Type {0} does not allow for target Profiles
|
||||
TERMINOLOGY_TX_NOSVC_BOUND_REQ = Could not confirm that the codes provided are from the required value set {0} because there is no terminology service
|
||||
TERMINOLOGY_TX_NOSVC_BOUND_EXT = Could not confirm that the codes provided are from the extensible value set {0} because there is no terminology service
|
||||
ARRAY_CANNOT_BE_EMPTY = Array cannot be empty - the property should not be present if it has no values
|
||||
|
||||
XHTML_URL_DATA_NO_DATA = No data found in data: URL
|
||||
XHTML_URL_DATA_DATA_INVALID_COMMA = Comma found in data portion of data URL: {0}
|
||||
XHTML_URL_DATA_DATA_INVALID = The data should be valid base64 content for a data: URL: {0}
|
||||
XHTML_URL_DATA_MIMETYPE = The mimetype potion of the data: URL is not valid ({1}) in URL: {0}
|
||||
|
|
|
@ -104,6 +104,7 @@ import org.hl7.fhir.utilities.TimeTracker;
|
|||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.json.JSONUtil;
|
||||
import org.hl7.fhir.utilities.json.JsonTrackingParser;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
|
@ -114,6 +115,7 @@ 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.xhtml.XhtmlComposer;
|
||||
import org.hl7.fhir.utilities.xml.XMLUtil;
|
||||
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
|
||||
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
|
||||
import org.hl7.fhir.validation.cli.model.ScanOutputItem;
|
||||
|
@ -122,6 +124,9 @@ import org.hl7.fhir.validation.cli.utils.*;
|
|||
import org.hl7.fhir.validation.instance.InstanceValidator;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xmlpull.v1.builder.XmlDocument;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
|
||||
/*
|
||||
|
@ -1310,6 +1315,22 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
|
|||
if (s.contains("http://hl7.org/fhir/1.4")) {
|
||||
versions.see("1.4", "Profile in "+ref);
|
||||
}
|
||||
try {
|
||||
if (s.startsWith("{")) {
|
||||
JsonObject json = JsonTrackingParser.parse(s, null);
|
||||
if (json.has("fhirVersion")) {
|
||||
versions.see(VersionUtilities.getMajMin(JSONUtil.str(json, "fhirVersion")), "fhirVersion in "+ref);
|
||||
}
|
||||
} else {
|
||||
Document doc = parseXml(cnt.focus);
|
||||
String v = XMLUtil.getNamedChildValue(doc.getDocumentElement(), "fhirVersion");
|
||||
if (v != null) {
|
||||
versions.see(VersionUtilities.getMajMin(v), "fhirVersion in "+ref);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1883,7 +1904,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
|
||||
public boolean resolveURL(Object appContext, String path, String url, String type) throws IOException, FHIRException {
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) { // ignore these
|
||||
return true;
|
||||
}
|
||||
|
@ -1898,7 +1919,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
|
|||
return true;
|
||||
}
|
||||
if (fetcher != null) {
|
||||
return fetcher.resolveURL(appContext, path, url);
|
||||
return fetcher.resolveURL(appContext, path, url, type);
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package org.hl7.fhir.validation.cli.services;
|
|||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
|
||||
|
@ -18,10 +20,16 @@ import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
|
|||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
|
||||
import org.hl7.fhir.utilities.json.JSONUtil;
|
||||
import org.hl7.fhir.utilities.json.JsonTrackingParser;
|
||||
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import ca.uhn.fhir.util.JsonUtil;
|
||||
|
||||
public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
|
||||
|
||||
public interface IPackageInstaller {
|
||||
|
@ -29,6 +37,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
|
|||
void loadPackage(String id, String ver) throws IOException, FHIRException;
|
||||
}
|
||||
|
||||
List<String> mappingsUris = new ArrayList<>();
|
||||
private FilesystemPackageCacheManager pcm;
|
||||
private IWorkerContext context;
|
||||
private IPackageInstaller installer;
|
||||
|
@ -51,11 +60,19 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
|
||||
public boolean resolveURL(Object appContext, String path, String url, String type) throws IOException, FHIRException {
|
||||
if (!Utilities.isAbsoluteUrl(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (url.contains("|")) {
|
||||
url = url.substring(0, url.lastIndexOf("|"));
|
||||
}
|
||||
|
||||
if (type.equals("uri") && isMappingUri(url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we've got to here, it's a reference to a FHIR URL. We're going to try to resolve it on the fly
|
||||
String pid = null;
|
||||
String ver = null;
|
||||
|
@ -94,10 +111,70 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// we don't bother with urls outside fhir space in the standalone validator - we assume they are valid
|
||||
return !url.startsWith("http://hl7.org/fhir");
|
||||
}
|
||||
|
||||
private boolean isMappingUri(String url) {
|
||||
if (mappingsUris.isEmpty()) {
|
||||
JsonObject json;
|
||||
try {
|
||||
json = JsonTrackingParser.fetchJson("http://hl7.org/fhir/mappingspaces.json");
|
||||
for (JsonObject ms : JSONUtil.objects(json, "spaces")) {
|
||||
mappingsUris.add(JSONUtil.str(ms, "url"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// frozen R4 list
|
||||
mappingsUris.add("http://hl7.org/fhir/fivews");
|
||||
mappingsUris.add("http://hl7.org/fhir/workflow");
|
||||
mappingsUris.add("http://hl7.org/fhir/interface");
|
||||
mappingsUris.add("http://hl7.org/v2");
|
||||
mappingsUris.add("http://loinc.org");
|
||||
mappingsUris.add("http://snomed.org/attributebinding");
|
||||
mappingsUris.add("http://snomed.info/conceptdomain");
|
||||
mappingsUris.add("http://hl7.org/v3/cda");
|
||||
mappingsUris.add("http://hl7.org/v3");
|
||||
mappingsUris.add("http://nema.org/dicom");
|
||||
mappingsUris.add("http://w3.org/vcard");
|
||||
mappingsUris.add("http://ihe.net/xds");
|
||||
mappingsUris.add("http://www.w3.org/ns/prov");
|
||||
mappingsUris.add("http://ietf.org/rfc/2445");
|
||||
mappingsUris.add("http://www.omg.org/spec/ServD/1.0/");
|
||||
mappingsUris.add("http://metadata-standards.org/11179/");
|
||||
mappingsUris.add("http://ihe.net/data-element-exchange");
|
||||
mappingsUris.add("http://openehr.org");
|
||||
mappingsUris.add("http://siframework.org/ihe-sdc-profile");
|
||||
mappingsUris.add("http://siframework.org/cqf");
|
||||
mappingsUris.add("http://www.cdisc.org/define-xml");
|
||||
mappingsUris.add("http://www.cda-adc.ca/en/services/cdanet/");
|
||||
mappingsUris.add("http://www.pharmacists.ca/");
|
||||
mappingsUris.add("http://www.healthit.gov/quality-data-model");
|
||||
mappingsUris.add("http://hl7.org/orim");
|
||||
mappingsUris.add("http://hl7.org/fhir/w5");
|
||||
mappingsUris.add("http://hl7.org/fhir/logical");
|
||||
mappingsUris.add("http://hl7.org/fhir/auditevent");
|
||||
mappingsUris.add("http://hl7.org/fhir/provenance");
|
||||
mappingsUris.add("http://hl7.org/qidam");
|
||||
mappingsUris.add("http://cap.org/ecc");
|
||||
mappingsUris.add("http://fda.gov/UDI");
|
||||
mappingsUris.add("http://hl7.org/fhir/object-implementation");
|
||||
mappingsUris.add("http://github.com/MDMI/ReferentIndexContent");
|
||||
mappingsUris.add("http://ncpdp.org/SCRIPT10_6");
|
||||
mappingsUris.add("http://clinicaltrials.gov");
|
||||
mappingsUris.add("http://hl7.org/fhir/rr");
|
||||
mappingsUris.add("http://www.hl7.org/v3/PORX_RM020070UV");
|
||||
mappingsUris.add("https://bridgmodel.nci.nih.gov");
|
||||
mappingsUris.add("http://hl7.org/fhir/composition");
|
||||
mappingsUris.add("http://hl7.org/fhir/documentreference");
|
||||
mappingsUris.add("https://en.wikipedia.org/wiki/Identification_of_medicinal_products");
|
||||
mappingsUris.add("urn:iso:std:iso:11073:10201");
|
||||
mappingsUris.add("urn:iso:std:iso:11073:10207");
|
||||
}
|
||||
}
|
||||
return mappingsUris.contains(url);
|
||||
}
|
||||
|
||||
private String findBaseUrl(String url) {
|
||||
String[] p = url.split("\\/");
|
||||
for (int i = 1; i< p.length; i++) {
|
||||
|
|
|
@ -1927,11 +1927,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
// for now, no validation. Need to think about authority.
|
||||
} else {
|
||||
// now, do we check the URI target?
|
||||
if (fetcher != null) {
|
||||
if (fetcher != null && !type.equals("uuid")) {
|
||||
boolean found;
|
||||
try {
|
||||
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url) ||
|
||||
SpecialExtensions.isKnownExtension(url) || isXverUrl(url);
|
||||
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) ||
|
||||
SpecialExtensions.isKnownExtension(url) || isXverUrl(url) || fetcher.resolveURL(appContext, path, url, type);
|
||||
} catch (IOException e1) {
|
||||
found = false;
|
||||
}
|
||||
|
@ -2228,19 +2228,46 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
return context.formatMessage(I18nConstants.XHTML_URL_EMPTY);
|
||||
}
|
||||
|
||||
Set<Character> invalidChars = new HashSet<>();
|
||||
for (char ch : value.toCharArray()) {
|
||||
if (!(Character.isDigit(ch) || Character.isAlphabetic(ch) || Utilities.existsInList(ch, ';', '?', ':', '@', '&', '=', '+', '$', '.', ',', '/', '%', '-', '_', '~', '#', '[', ']', '!', '\'', '(', ')', '*' ))) {
|
||||
invalidChars.add(ch);
|
||||
if (value.startsWith("data:")) {
|
||||
String[] p = value.substring(5).split("\\,");
|
||||
if (p.length < 2) {
|
||||
return context.formatMessage(I18nConstants.XHTML_URL_DATA_NO_DATA, value);
|
||||
} else if (p.length > 2) {
|
||||
return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID_COMMA, value);
|
||||
} else if (!p[0].endsWith(";base64") || !isValidBase64(p[1])) {
|
||||
return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID, value);
|
||||
} else {
|
||||
if (p[0].startsWith(" ")) {
|
||||
p[0] = p[0].trim();
|
||||
}
|
||||
String mMsg = checkValidMimeType(p[0].substring(0, p[0].lastIndexOf(";")));
|
||||
if (mMsg != null) {
|
||||
return context.formatMessage(I18nConstants.XHTML_URL_DATA_MIMETYPE, value, mMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalidChars.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return context.formatMessage(I18nConstants.XHTML_URL_INVALID_CHARS, invalidChars.toString());
|
||||
Set<Character> invalidChars = new HashSet<>();
|
||||
for (char ch : value.toCharArray()) {
|
||||
if (!(Character.isDigit(ch) || Character.isAlphabetic(ch) || Utilities.existsInList(ch, ';', '?', ':', '@', '&', '=', '+', '$', '.', ',', '/', '%', '-', '_', '~', '#', '[', ']', '!', '\'', '(', ')', '*' ))) {
|
||||
invalidChars.add(ch);
|
||||
}
|
||||
}
|
||||
if (invalidChars.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return context.formatMessage(I18nConstants.XHTML_URL_INVALID_CHARS, invalidChars.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String checkValidMimeType(String mt) {
|
||||
if (!mt.matches("^(\\w+|\\*)\\/(\\w+|\\*)((;\\s*(\\w+)=\\s*(\\S+))?)$")) {
|
||||
return "Mime type invalid";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
|
||||
for (XhtmlNode node : list) {
|
||||
if (node.getNodeType() == NodeType.Element) {
|
||||
|
|
|
@ -223,7 +223,7 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), sd.getKind() == StructureDefinitionKind.RESOURCE, I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Resource");
|
||||
}
|
||||
}
|
||||
} else if (code.equals("canonical")) {
|
||||
|
@ -232,7 +232,7 @@ public class StructureDefinitionValidator extends BaseValidator {
|
|||
if (t == null) {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
|
||||
} else {
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code, path);
|
||||
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -490,7 +490,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
|
||||
public boolean resolveURL(Object appContext, String path, String url, String type) throws IOException, FHIRException {
|
||||
return !url.contains("example.org") && !url.startsWith("http://hl7.org/fhir/invalid");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue