Add matchetype pattern validation

This commit is contained in:
Grahame Grieve 2025-01-30 00:51:54 +11:00
parent c0bcdc6293
commit ac4f050bac
8 changed files with 599 additions and 497 deletions

View File

@ -1189,4 +1189,9 @@ public class I18nConstants {
public static final String VALIDATION_AI_FAILED = "VALIDATION_AI_FAILED";
public static final String VALIDATION_AI_FAILED_LOG = "VALIDATION_AI_FAILED_LOG";
public static final String RESOURCE_DUPLICATE_CONTAINED_ID = "RESOURCE_DUPLICATE_CONTAINED_ID";
public static final String RESOURCE_MATCHETYPE_REQUIRED = "RESOURCE_MATCHETYPE_REQUIRED";
public static final String RESOURCE_MATCHETYPE_DISALLOWED = "RESOURCE_MATCHETYPE_DISALLOWED";
public static final String RESOURCE_NOT_MATCHETYPE_EXTENSION = "RESOURCE_NOT_MATCHETYPE_EXTENSION";
public static final String RESOURCE_MATCHETYPE_SUSPECT_TYPE = "RESOURCE_MATCHETYPE_SUSPECT_TYPE";
public static final String RESOURCE_MATCHETYPE_UNKNOWN_PATTERN = "RESOURCE_MATCHETYPE_UNKNOWN_PATTERN";
}

View File

@ -1220,4 +1220,8 @@ VALIDATION_AI_TEXT_CODE = Apparent mis-match between code ''{0}'' and text ''{1}
VALIDATION_AI_FAILED = Consulting AI failed: {0}
VALIDATION_AI_FAILED_LOG = Consulting AI failed: {0} (see {1} for further details)
RESOURCE_DUPLICATE_CONTAINED_ID = Duplicate ID for contained resource: {0}
RESOURCE_MATCHETYPE_REQUIRED = This resource is required to be a matchetype resource (extension http://hl7.org/fhir/tools/StructureDefinition/matchetype must be true)
RESOURCE_MATCHETYPE_DISALLOWED = This resource is not allowed to be a matchetype resource (extension http://hl7.org/fhir/tools/StructureDefinition/matchetype is true)
RESOURCE_NOT_MATCHETYPE_EXTENSION = This resource is not a matcehtype, so it cannot have the extension {0}
RESOURCE_MATCHETYPE_SUSPECT_TYPE = The type ''{0}'' does not appear to match the matchetype pattern value ''{1}''
RESOURCE_MATCHETYPE_UNKNOWN_PATTERN = The matchetype pattern value ''{1}'' is not valid

View File

@ -197,6 +197,7 @@ import org.hl7.fhir.validation.ai.CodeAndTextValidator;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.codesystem.CodingsObserver;
import org.hl7.fhir.validation.instance.InstanceValidator.MatchetypeStatus;
import org.hl7.fhir.validation.instance.type.BundleValidator;
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.ConceptMapValidator;
@ -245,6 +246,10 @@ import org.w3c.dom.Document;
public class InstanceValidator extends BaseValidator implements IResourceValidator {
public enum MatchetypeStatus {
Disallowed, Allowed, Required
}
public enum BindingContext {
BASE, MAXVS, ADDITIONAL
@ -627,6 +632,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private String aiService;
private Set<Integer> textsToCheckKeys = new HashSet<>();
private String cacheFolder;
private MatchetypeStatus matchetypeStatus = MatchetypeStatus.Disallowed;
public InstanceValidator(@Nonnull IWorkerContext theContext, @Nonnull IEvaluationContext hostServices, @Nonnull XVerExtensionManager xverManager, ValidatorSession session) {
super(theContext, xverManager, false, session);
@ -2447,6 +2453,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String u = url.contains("|") ? url.substring(0, url.indexOf("|")) : url;
boolean isModifier = element.getName().equals("modifierExtension");
assert def.getIsModifier() == isModifier;
ok = rule(errors, "2025-01-28", IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']",
valContext.isMatchetype() || !Utilities.existsInList(url, "http://hl7.org/fhir/tools/StructureDefinition/matchetype-optional",
"http://hl7.org/fhir/tools/StructureDefinition/matchetype-count", "http://hl7.org/fhir/tools/StructureDefinition/matchetype-value"),
I18nConstants.RESOURCE_NOT_MATCHETYPE_EXTENSION, url) && ok;
long t = System.nanoTime();
StructureDefinition ex = Utilities.isAbsoluteUrl(u) ? context.fetchResource(StructureDefinition.class, u) : null;
@ -3072,336 +3083,380 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
warningPlural(errors, "2023-07-26", IssueType.INVALID, e.line(), e.col(), path, badChars.isEmpty(), badChars.size(), I18nConstants.UNICODE_XML_BAD_CHARS, badChars.toString());
}
if (context.hasExtension(ToolingExtensions.EXT_MIN_LENGTH) && e.hasPrimitiveValue()) {
int length = e.primitiveValue().length();
int spec = ToolingExtensions.readIntegerExtension(context, ToolingExtensions.EXT_MIN_LENGTH, 0);
ok = rule(errors, "2024-11-02", IssueType.INVALID, e.line(), e.col(), path, length >= spec, I18nConstants.PRIMITIVE_TOO_SHORT, length, spec) && ok;
}
String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
// there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons
//( see task 13328) it might also be found on one the types
if (regex != null) {
for (TypeRefComponent tr : context.getType()) {
if (tr.hasExtension(ToolingExtensions.EXT_REGEX)) {
regex = tr.getExtensionString(ToolingExtensions.EXT_REGEX);
break;
}
}
}
if (regex != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex) && ok;
}
if (!"xhtml".equals(type)) {
if (securityChecks) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR) && ok;
} else if (!"markdown".equals(type)){
hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING);
}
}
if (type.equals("boolean")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE) && ok;
}
if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
String url = e.primitiveValue();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.equals(Utilities.trimWS(url).replace(" ", ""))
// work around an old invalid example in a core package
|| "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_WS, url) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
if (valContext.isMatchetype() && e.primitiveValue().startsWith("$")) {
switch (e.primitiveValue()) {
case "$semver$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "code"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$url$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "url", "uri", "uuid", "oid", "canonical"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$token$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "code"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$string$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "string"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$date$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "date", "dateTime"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$version$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "code"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$id$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "id"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$instant$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "dateTime", "instant"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$uuid$":
warning(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(type, "url", "uri", "uuid", "canonical"), I18nConstants.RESOURCE_MATCHETYPE_SUSPECT_TYPE, type, e.primitiveValue());
break;
case "$$":
break;
default:
if (e.primitiveValue().startsWith("$external:")) {
if (type.equals("oid")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START) && ok;
}
if (type.equals("uuid")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT) && ok;
}
if (type.equals("canonical")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;
}
} else if (e.primitiveValue().startsWith("$choice:")) {
if (url != null && url.startsWith("urn:uuid:")) {
String s = url.substring(9);
if (s.contains("#")) {
s = s.substring(0, s.indexOf("#"));
} else if (e.primitiveValue().startsWith("$fragments:")) {
} else {
rule(errors, "2025-01-28", IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.RESOURCE_MATCHETYPE_UNKNOWN_PATTERN, e.primitiveValue());
}
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(s), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID, s) && ok;
}
if (url != null && url.startsWith("urn:oid:")) {
String cc = url.substring(8);
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
// OIDs roots shorter than 4 chars are almost never valid for namespaces except for 1.3.x
Utilities.isOid(cc) && ((cc.lastIndexOf('.') >= 4 || cc.startsWith("1.3"))),
I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID, cc) && ok;
} else {
if (context.hasExtension(ToolingExtensions.EXT_MIN_LENGTH) && e.hasPrimitiveValue()) {
int length = e.primitiveValue().length();
int spec = ToolingExtensions.readIntegerExtension(context, ToolingExtensions.EXT_MIN_LENGTH, 0);
ok = rule(errors, "2024-11-02", IssueType.INVALID, e.line(), e.col(), path, length >= spec, I18nConstants.PRIMITIVE_TOO_SHORT, length, spec) && ok;
}
String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
// there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons
//( see task 13328) it might also be found on one the types
if (regex != null) {
for (TypeRefComponent tr : context.getType()) {
if (tr.hasExtension(ToolingExtensions.EXT_REGEX)) {
regex = tr.getExtensionString(ToolingExtensions.EXT_REGEX);
break;
}
}
}
if (regex != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex) && ok;
}
if (isCanonicalURLElement(e, node)) {
// we get to here if this is a defining canonical URL (e.g. CodeSystem.url)
// the URL must be an IRI if present
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url),
node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;
} else if (!e.getProperty().getDefinition().getPath().equals("Bundle.entry.fullUrl")) { // we don't check fullUrl here; it's not a reference, it's a definition. It'll get checked as part of checking the bundle
ok = validateReference(valContext, errors, path, type, context, e, url) && ok;
}
}
if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) {
// work around an old issue with ElementDefinition.id
if (!context.getPath().equals("ElementDefinition.id")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()) && ok;
}
}
if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY)) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || !Utilities.isAllWhitespace(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS_ALL, prepWSPresentation(e.primitiveValue()))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || Utilities.trimWS(e.primitiveValue()).equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS, prepWSPresentation(e.primitiveValue()));
if (!"xhtml".equals(type)) {
if (securityChecks) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR) && ok;
} else if (!"markdown".equals(type)){
hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING);
}
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
}
if (type.equals("boolean")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE) && ok;
}
if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
String url = e.primitiveValue();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.equals(Utilities.trimWS(url).replace(" ", ""))
// work around an old invalid example in a core package
|| "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_WS, url) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
if (type.equals("oid")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START) && ok;
}
if (type.equals("uuid")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT) && ok;
}
if (type.equals("canonical")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;
}
if (url != null && url.startsWith("urn:uuid:")) {
String s = url.substring(9);
if (s.contains("#")) {
s = s.substring(0, s.indexOf("#"));
}
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(s), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID, s) && ok;
}
if (url != null && url.startsWith("urn:oid:")) {
String cc = url.substring(8);
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
// OIDs roots shorter than 4 chars are almost never valid for namespaces except for 1.3.x
Utilities.isOid(cc) && ((cc.lastIndexOf('.') >= 4 || cc.startsWith("1.3"))),
I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID, cc) && ok;
}
if (isCanonicalURLElement(e, node)) {
// we get to here if this is a defining canonical URL (e.g. CodeSystem.url)
// the URL must be an IRI if present
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url),
node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;
} else if (!e.getProperty().getDefinition().getPath().equals("Bundle.entry.fullUrl")) { // we don't check fullUrl here; it's not a reference, it's a definition. It'll get checked as part of checking the bundle
ok = validateReference(valContext, errors, path, type, context, e, url) && ok;
}
}
if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) {
// work around an old issue with ElementDefinition.id
if (!context.getPath().equals("ElementDefinition.id")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()) && ok;
}
}
if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY)) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || !Utilities.isAllWhitespace(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS_ALL, prepWSPresentation(e.primitiveValue()))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || Utilities.trimWS(e.primitiveValue()).equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS, prepWSPresentation(e.primitiveValue()));
}
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
} else {
ok = false;
}
} else {
ok = false;
}
} else {
ok = false;
}
}
if (type.equals("dateTime")) {
boolean dok = ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for dateTime") && ok;
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("tz-for-time"))) {
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && dok;
}
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok;
if (dok) {
try {
DateTimeType dt = new DateTimeType(e.primitiveValue());
if (isCoreDefinition(profile) || !context.hasExtension(ToolingExtensions.EXT_DATE_RULES) || ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid")) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage());
dok = false;
if (type.equals("dateTime")) {
boolean dok = ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for dateTime") && ok;
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("tz-for-time"))) {
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && dok;
}
}
ok = ok && dok;
}
if (type.equals("time")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID) && ok;
try {
TimeType dt = new TimeType(e.primitiveValue());
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage());
ok = false;
}
}
if (type.equals("date")) {
boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for date");
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok;
if (dok) {
try {
DateType dt = new DateType(e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("base64Binary")) {
String encoded = e.primitiveValue();
if (isNotBlank(encoded)) {
boolean bok = Base64Util.isValidBase64(encoded);
if (!bok) {
String value = encoded.length() < 100 ? encoded : "(snip)";
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value) && ok;
} else {
boolean wsok = !Base64Util.base64HasWhitespace(encoded);
if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR) && ok;
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING);
}
}
if (bok && context.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
int size = Base64Util.countBase64DecodedBytes(encoded);
long def = Long.parseLong(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_SIZE));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def) && ok;
}
}
}
if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_VALID, e.primitiveValue())) {
Integer v = Integer.valueOf(e.getValue());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")) && ok;
if (type.equals("unsignedInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
ok = false;
}
}
if (type.equals("integer64")) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER64_VALID, e.primitiveValue())) {
Long v = Long.valueOf(e.getValue());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueInteger64Type() || !context.getMaxValueInteger64Type().hasValue() || (context.getMaxValueInteger64Type().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueInteger64Type() ? context.getMaxValueInteger64Type() : "")) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueInteger64Type() || !context.getMinValueInteger64Type().hasValue() || (context.getMinValueInteger64Type().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueInteger64Type() ? context.getMinValueInteger64Type() : "")) && ok;
if (type.equals("unsignedInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
ok = false;
}
}
if (type.equals("decimal")) {
if (e.primitiveValue() != null) {
DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) {
warning(errors, NO_RULE_DATE, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue());
try {
Decimal v = new Decimal(e.getValue());
if (context.hasMaxValueDecimalType() && context.getMaxValueDecimalType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueDecimalType()) && ok;
} else if (context.hasMaxValueIntegerType() && context.getMaxValueIntegerType().hasValue()) {
// users can also provide a max integer type. It's not clear whether that's actually valid, but we'll check for it anyway
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, new BigDecimal(context.getMaxValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueIntegerType()) && ok;
}
if (context.hasMinValueDecimalType() && context.getMinValueDecimalType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, context.getMinValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMinValueDecimalType()) && ok;
} else if (context.hasMinValueIntegerType() && context.getMinValueIntegerType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, new BigDecimal(context.getMinValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMinValueIntegerType()) && ok;
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok;
if (dok) {
try {
DateTimeType dt = new DateTimeType(e.primitiveValue());
if (isCoreDefinition(profile) || !context.hasExtension(ToolingExtensions.EXT_DATE_RULES) || ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid")) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
// should never happen?
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("time")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID) && ok;
try {
TimeType dt = new TimeType(e.primitiveValue());
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage());
ok = false;
}
}
if (type.equals("date")) {
boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for date");
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok;
if (dok) {
try {
DateType dt = new DateType(e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("base64Binary")) {
String encoded = e.primitiveValue();
if (isNotBlank(encoded)) {
boolean bok = Base64Util.isValidBase64(encoded);
if (!bok) {
String value = encoded.length() < 100 ? encoded : "(snip)";
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value) && ok;
} else {
boolean wsok = !Base64Util.base64HasWhitespace(encoded);
if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR) && ok;
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING);
}
}
if (bok && context.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
int size = Base64Util.countBase64DecodedBytes(encoded);
long def = Long.parseLong(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_SIZE));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def) && ok;
}
}
}
if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_VALID, e.primitiveValue())) {
Integer v = Integer.valueOf(e.getValue());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")) && ok;
if (type.equals("unsignedInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
ok = false;
}
}
if (context.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0;
int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_DECIMALS));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def) && ok;
}
}
if (type.equals("instant")) {
boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX, "'"+e.primitiveValue()+"' doesn't meet format requirements for instant)");
if (dok) {
try {
InstantType dt = new InstantType(e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("code") && e.primitiveValue() != null) {
// Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace
// other than single spaces in the contents
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CODE_WS, e.primitiveValue()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
}
if (context.hasBinding() && e.primitiveValue() != null) {
// special cases
if ("StructureDefinition.type".equals(context.getPath()) && "http://hl7.org/fhir/StructureDefinition/StructureDefinition".equals(profile.getUrl())) {
ok = checkTypeValue(errors, path, e, parentNode.getElement());
} else {
ok = checkPrimitiveBinding(valContext, errors, path, type, context, e, profile, node) && ok;
}
}
if (type.equals("markdown") && htmlInMarkdownCheck != HtmlInMarkdownCheck.NONE) {
String raw = e.primitiveValue();
String processed = MarkDownProcessor.preProcess(raw);
if (!raw.equals(processed)) {
int i = 0;
while (i < raw.length() && raw.charAt(1) == processed.charAt(i)) {
i++;
}
if (i < raw.length()-1 ) {
if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw.subSequence(i, i+2))) {
ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok;
}
if (type.equals("integer64")) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER64_VALID, e.primitiveValue())) {
Long v = Long.valueOf(e.getValue());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueInteger64Type() || !context.getMaxValueInteger64Type().hasValue() || (context.getMaxValueInteger64Type().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueInteger64Type() ? context.getMaxValueInteger64Type() : "")) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueInteger64Type() || !context.getMinValueInteger64Type().hasValue() || (context.getMinValueInteger64Type().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueInteger64Type() ? context.getMinValueInteger64Type() : "")) && ok;
if (type.equals("unsignedInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw)) {
ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok;
}
ok = false;
}
}
}
if (type.equals("xhtml")) {
XhtmlNode xhtml = e.getXhtml();
if (xhtml != null) { // if it is null, this is an error already noted in the parsers
// check that the namespace is there and correct.
String ns = xhtml.getNsDecl();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS) && ok;
// check that inner namespaces are all correct
ok = checkInnerNS(errors, e, path, xhtml.getChildNodes()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, xhtml.getName()) && ok;
// check that no illegal elements and attributes have been used
ok = checkInnerNames(errors, e, path, xhtml.getChildNodes(), false) && ok;
ok = checkUrls(errors, e, path, xhtml.getChildNodes()) && ok;
ok = checkIdRefs(errors, e, path, xhtml, resource, node) && ok;
if (true) {
ok = checkReferences(valContext, errors, e, path, "div", xhtml, resource) && ok;
if (type.equals("decimal")) {
if (e.primitiveValue() != null) {
DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) {
warning(errors, NO_RULE_DATE, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue());
try {
Decimal v = new Decimal(e.getValue());
if (context.hasMaxValueDecimalType() && context.getMaxValueDecimalType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueDecimalType()) && ok;
} else if (context.hasMaxValueIntegerType() && context.getMaxValueIntegerType().hasValue()) {
// users can also provide a max integer type. It's not clear whether that's actually valid, but we'll check for it anyway
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, new BigDecimal(context.getMaxValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueIntegerType()) && ok;
}
if (context.hasMinValueDecimalType() && context.getMinValueDecimalType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, context.getMinValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMinValueDecimalType()) && ok;
} else if (context.hasMinValueIntegerType() && context.getMinValueIntegerType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, new BigDecimal(context.getMinValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMinValueIntegerType()) && ok;
}
} catch (Exception ex) {
// should never happen?
}
} else {
ok = false;
}
}
if (true) {
ok = checkImageSources(valContext, errors, e, path, "div", xhtml, resource) && ok;
if (context.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0;
int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_DECIMALS));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def) && ok;
}
}
}
if (context.hasFixed()) {
ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, profile.getVersionedUrl()+"#"+context.getId()) && ok;
}
if (context.hasPattern()) {
ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, profile.getVersionedUrl()+"#"+context.getId()) && ok;
}
if (ok && !ID_EXEMPT_LIST.contains(e.fhirType())) { // ids get checked elsewhere
String regext = FHIRPathExpressionFixer.fixRegex(getRegexFromType(e.fhirType()));
if (regext != null) {
try {
String pt = e.primitiveValue();
String ptFmt = null;
if (e.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
ptFmt = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(e.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), pt);
if (type.equals("instant")) {
boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX, "'"+e.primitiveValue()+"' doesn't meet format requirements for instant)");
if (dok) {
try {
InstantType dt = new InstantType(e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage());
dok = false;
}
boolean matches = pt.matches(regext) || (ptFmt != null && ptFmt.matches(regext));
if (!matches) {
if (ptFmt == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, pt, e.fhirType(), regext) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE_ALT, pt, ptFmt, e.fhirType(), regext) && ok;
}
ok = ok && dok;
}
if (type.equals("code") && e.primitiveValue() != null) {
// Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace
// other than single spaces in the contents
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CODE_WS, e.primitiveValue()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
}
if (context.hasBinding() && e.primitiveValue() != null) {
// special cases
if ("StructureDefinition.type".equals(context.getPath()) && "http://hl7.org/fhir/StructureDefinition/StructureDefinition".equals(profile.getUrl())) {
ok = checkTypeValue(errors, path, e, parentNode.getElement());
} else {
ok = checkPrimitiveBinding(valContext, errors, path, type, context, e, profile, node) && ok;
}
}
if (type.equals("markdown") && htmlInMarkdownCheck != HtmlInMarkdownCheck.NONE) {
String raw = e.primitiveValue();
String processed = MarkDownProcessor.preProcess(raw);
if (!raw.equals(processed)) {
int i = 0;
while (i < raw.length() && raw.charAt(1) == processed.charAt(i)) {
i++;
}
if (i < raw.length()-1 ) {
if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw.subSequence(i, i+2))) {
ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok;
}
} else {
if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw)) {
ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok;
}
}
} catch (Throwable ex) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION, regext, e.fhirType(), ex.getMessage()) && ok;
}
}
if (type.equals("xhtml")) {
XhtmlNode xhtml = e.getXhtml();
if (xhtml != null) { // if it is null, this is an error already noted in the parsers
// check that the namespace is there and correct.
String ns = xhtml.getNsDecl();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS) && ok;
// check that inner namespaces are all correct
ok = checkInnerNS(errors, e, path, xhtml.getChildNodes()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, xhtml.getName()) && ok;
// check that no illegal elements and attributes have been used
ok = checkInnerNames(errors, e, path, xhtml.getChildNodes(), false) && ok;
ok = checkUrls(errors, e, path, xhtml.getChildNodes()) && ok;
ok = checkIdRefs(errors, e, path, xhtml, resource, node) && ok;
if (true) {
ok = checkReferences(valContext, errors, e, path, "div", xhtml, resource) && ok;
}
if (true) {
ok = checkImageSources(valContext, errors, e, path, "div", xhtml, resource) && ok;
}
}
}
if (context.hasFixed()) {
ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, profile.getVersionedUrl()+"#"+context.getId()) && ok;
}
if (context.hasPattern()) {
ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, profile.getVersionedUrl()+"#"+context.getId()) && ok;
}
if (ok && !ID_EXEMPT_LIST.contains(e.fhirType())) { // ids get checked elsewhere
String regext = FHIRPathExpressionFixer.fixRegex(getRegexFromType(e.fhirType()));
if (regext != null) {
try {
String pt = e.primitiveValue();
String ptFmt = null;
if (e.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
ptFmt = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(e.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), pt);
}
boolean matches = pt.matches(regext) || (ptFmt != null && ptFmt.matches(regext));
if (!matches) {
if (ptFmt == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, pt, e.fhirType(), regext) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE_ALT, pt, ptFmt, e.fhirType(), regext) && ok;
}
}
} catch (Throwable ex) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION, regext, e.fhirType(), ex.getMessage()) && ok;
}
}
}
}
// for nothing to check
return ok;
}
@ -7618,7 +7673,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
*/
private boolean validateResource(ValidationContext valContext, List<ValidationMessage> errors, Element resource,
Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack, PercentageTracker pct, ValidationMode mode, boolean forReference, boolean fromContained) throws FHIRException {
boolean ok = true;
// check here if we call validation policy here, and then change it to the new interface
assert stack != null;
@ -7669,6 +7723,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
}
boolean isMatcheType = false;
if (element.hasExtension("http://hl7.org/fhir/tools/StructureDefinition/matchetype")) {
Element ext = element.getExtension("http://hl7.org/fhir/tools/StructureDefinition/matchetype");
isMatcheType = "true".equals(ext.getNamedChildValue("value"));
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), matchetypeStatus != MatchetypeStatus.Disallowed, I18nConstants.RESOURCE_MATCHETYPE_DISALLOWED) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), matchetypeStatus != MatchetypeStatus.Required, I18nConstants.RESOURCE_MATCHETYPE_REQUIRED) && ok;
}
valContext.setMatchetype(isMatcheType);
// validate
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), checkResourceName(defn, resourceName, element.getFormat()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE,
defn.getType(), resourceName, defn.getVersionedUrl())) {
@ -8265,5 +8330,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public void setCacheFolder(String cacheFolder) {
this.cacheFolder = cacheFolder;
}
public MatchetypeStatus getMatchetypeStatus() {
return matchetypeStatus;
}
public void setMatchetypeStatus(MatchetypeStatus matchetypeStatus) {
this.matchetypeStatus = matchetypeStatus;
}
}

View File

@ -7,242 +7,256 @@ import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.validation.ValidationMessage;
public class ValidationContext {
public static final String INTERNAL_REFERENCES_NAME = "internal.references";
public static final String INTERNAL_REFERENCES_NAME = "internal.references";
private Object appContext;
private Object appContext;
// the version we are currently validating for right now
// not implemented yet - this is the forerunner of a major upgrade to the validator
private String version;
// the resource we are actually validating right now
private Element resource;
// the resource that is the scope of id resolution - either the same as resource, or the resource the contains that resource. This can only be one level deep.
private Element rootResource;
private Element groupingResource; // either a bundle or a parameters that holds the rootResource (for reference resolution)
private StructureDefinition profile; // the profile that contains the content being validated
private boolean checkSpecials = true;
private Map<String, List<ValidationMessage>> sliceRecords;
private Set<String> internalRefs;
public ValidationContext(Object appContext) {
this.appContext = appContext;
}
// the version we are currently validating for right now
// not implemented yet - this is the forerunner of a major upgrade to the validator
private String version;
public ValidationContext(Object appContext, Element element) {
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
this.internalRefs = setupInternalRefs(element);
check();
// no groupingResource (Bundle or Parameters)
dump("creating");
// the resource we are actually validating right now
private Element resource;
// the resource that is the scope of id resolution - either the same as resource, or the resource the contains that resource. This can only be one level deep.
private Element rootResource;
private Element groupingResource; // either a bundle or a parameters that holds the rootResource (for reference resolution)
private StructureDefinition profile; // the profile that contains the content being validated
private boolean checkSpecials = true;
private Map<String, List<ValidationMessage>> sliceRecords;
private Set<String> internalRefs;
private boolean matchetype;
public ValidationContext(Object appContext) {
this.appContext = appContext;
}
private Set<String> setupInternalRefs(Element element) {
Set<String> res = (Set<String>) element.getUserData(INTERNAL_REFERENCES_NAME);
if (res == null) {
res = new HashSet<String>();
element.setUserData(INTERNAL_REFERENCES_NAME, res);
}
return res;
}
public ValidationContext(Object appContext, Element element) {
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
this.internalRefs = setupInternalRefs(element);
check();
private void check() {
if (!rootResource.hasParentForValidator()) {
throw new Error("No parent on root resource");
}
}
public ValidationContext(Object appContext, Element element, Element root) {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
this.internalRefs = setupInternalRefs(element);
check();
// no groupingResource (Bundle or Parameters)
dump("creating");
// no groupingResource (Bundle or Parameters)
dump("creating");
}
public ValidationContext(Object appContext, Element element, Element root, Element groupingResource) {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
this.groupingResource = groupingResource;
this.internalRefs = setupInternalRefs(element);
check();
dump("creating");
private Set<String> setupInternalRefs(Element element) {
Set<String> res = (Set<String>) element.getUserData(INTERNAL_REFERENCES_NAME);
if (res == null) {
res = new HashSet<String>();
element.setUserData(INTERNAL_REFERENCES_NAME, res);
}
return res;
}
public Object getAppContext() {
return appContext;
private void check() {
if (!rootResource.hasParentForValidator()) {
throw new Error("No parent on root resource");
}
}
public ValidationContext setAppContext(Object appContext) {
this.appContext = appContext;
return this;
}
public ValidationContext(Object appContext, Element element, Element root) {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
this.internalRefs = setupInternalRefs(element);
check();
// no groupingResource (Bundle or Parameters)
dump("creating");
}
public ValidationContext setResource(Element resource) {
this.resource = resource;
return this;
}
public ValidationContext(Object appContext, Element element, Element root, Element groupingResource) {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
this.groupingResource = groupingResource;
this.internalRefs = setupInternalRefs(element);
check();
dump("creating");
}
public Element getRootResource() {
return rootResource;
}
public Object getAppContext() {
return appContext;
}
public ValidationContext setRootResource(Element rootResource) {
this.rootResource = rootResource;
dump("setting root resource");
return this;
}
public ValidationContext setAppContext(Object appContext) {
this.appContext = appContext;
return this;
}
public Element getGroupingResource() {
return groupingResource;
}
public ValidationContext setResource(Element resource) {
this.resource = resource;
return this;
}
public StructureDefinition getProfile() {
return profile;
}
public Element getRootResource() {
return rootResource;
}
public ValidationContext setProfile(StructureDefinition profile) {
this.profile = profile;
return this;
}
public ValidationContext setRootResource(Element rootResource) {
this.rootResource = rootResource;
dump("setting root resource");
return this;
}
public Map<String, List<ValidationMessage>> getSliceRecords() {
return sliceRecords;
}
public Element getGroupingResource() {
return groupingResource;
}
public ValidationContext setSliceRecords(Map<String, List<ValidationMessage>> sliceRecords) {
this.sliceRecords = sliceRecords;
return this;
}
public StructureDefinition getProfile() {
return profile;
}
public boolean isCheckSpecials() {
return checkSpecials;
}
public ValidationContext setProfile(StructureDefinition profile) {
this.profile = profile;
return this;
}
public void setCheckSpecials(boolean checkSpecials) {
this.checkSpecials = checkSpecials;
}
public Map<String, List<ValidationMessage>> getSliceRecords() {
return sliceRecords;
}
public Element getResource() {
return resource;
}
public ValidationContext setSliceRecords(Map<String, List<ValidationMessage>> sliceRecords) {
this.sliceRecords = sliceRecords;
return this;
}
public void sliceNotes(String url, List<ValidationMessage> record) {
if (sliceRecords != null) {
sliceRecords.put(url, record);
}
}
public boolean isCheckSpecials() {
return checkSpecials;
}
public ValidationContext forContained(Element element) {
ValidationContext res = new ValidationContext(appContext);
res.rootResource = resource;
res.resource = element;
res.profile = profile;
res.groupingResource = groupingResource;
res.version = version;
res.internalRefs = setupInternalRefs(element);
res.dump("forContained");
return res;
}
public void setCheckSpecials(boolean checkSpecials) {
this.checkSpecials = checkSpecials;
}
public ValidationContext forEntry(Element element, Element groupingResource) {
ValidationContext res = new ValidationContext(appContext);
res.rootResource = element;
res.resource = element;
res.profile = profile;
res.groupingResource = groupingResource;
res.version = version;
res.internalRefs = setupInternalRefs(element);
res.dump("forEntry");
return res;
}
public Element getResource() {
return resource;
}
public ValidationContext forProfile(StructureDefinition profile) {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = rootResource;
res.profile = profile;
res.version = version;
res.groupingResource = groupingResource;
res.internalRefs = internalRefs;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
res.dump("forProfile "+profile.getUrl());
return res;
public void sliceNotes(String url, List<ValidationMessage> record) {
if (sliceRecords != null) {
sliceRecords.put(url, record);
}
}
public ValidationContext forLocalReference(StructureDefinition profile, Element resource) {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.profile = profile;
res.groupingResource = groupingResource;
res.checkSpecials = false;
res.internalRefs = setupInternalRefs(resource);
res.dump("forLocalReference "+profile.getUrl());
res.version = version;
return res;
}
public ValidationContext forContained(Element element) {
ValidationContext res = new ValidationContext(appContext);
res.rootResource = resource;
res.resource = element;
res.profile = profile;
res.groupingResource = groupingResource;
res.version = version;
res.matchetype = matchetype;
res.internalRefs = setupInternalRefs(element);
res.dump("forContained");
return res;
}
private void dump(String ctxt) {
// System.out.println("** app = "+(appContext == null ? "(null)" : appContext.toString())+", res = "+resource.toString()+", root = "+rootResource.toString()+" ("+ctxt+")");
// if (rootResource.getName().equals("contained")) {
// System.out.println("** something is wrong!");
// }
}
public ValidationContext forEntry(Element element, Element groupingResource) {
ValidationContext res = new ValidationContext(appContext);
res.rootResource = element;
res.resource = element;
res.profile = profile;
res.groupingResource = groupingResource;
res.version = version;
res.matchetype = matchetype;
res.internalRefs = setupInternalRefs(element);
res.dump("forEntry");
return res;
}
public ValidationContext forRemoteReference(StructureDefinition profile, Element resource) {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.profile = profile;
res.groupingResource = null;
res.checkSpecials = false;
res.version = version;
res.internalRefs = setupInternalRefs(resource);
res.dump("forRemoteReference "+profile.getUrl());
return res;
}
public ValidationContext forProfile(StructureDefinition profile) {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = rootResource;
res.profile = profile;
res.version = version;
res.matchetype = matchetype;
res.groupingResource = groupingResource;
res.internalRefs = internalRefs;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
res.dump("forProfile "+profile.getUrl());
return res;
}
public ValidationContext forSlicing() {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = rootResource;
res.groupingResource = groupingResource;
res.profile = profile;
res.checkSpecials = false;
res.version = version;
res.internalRefs = internalRefs;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
res.dump("forSlicing");
return res;
}
public ValidationContext forLocalReference(StructureDefinition profile, Element resource) {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.profile = profile;
res.groupingResource = groupingResource;
res.checkSpecials = false;
res.matchetype = matchetype;
res.internalRefs = setupInternalRefs(resource);
res.dump("forLocalReference "+profile.getUrl());
res.version = version;
return res;
}
public String getVersion() {
return version;
}
private void dump(String ctxt) {
// System.out.println("** app = "+(appContext == null ? "(null)" : appContext.toString())+", res = "+resource.toString()+", root = "+rootResource.toString()+" ("+ctxt+")");
// if (rootResource.getName().equals("contained")) {
// System.out.println("** something is wrong!");
// }
}
public ValidationContext setVersion(String version) {
this.version = version;
return this;
}
public ValidationContext forRemoteReference(StructureDefinition profile, Element resource) {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.profile = profile;
res.groupingResource = null;
res.checkSpecials = false;
res.version = version;
res.matchetype = matchetype;
res.internalRefs = setupInternalRefs(resource);
res.dump("forRemoteReference "+profile.getUrl());
return res;
}
public Set<String> getInternalRefs() {
return internalRefs;
}
public ValidationContext forSlicing() {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = rootResource;
res.groupingResource = groupingResource;
res.profile = profile;
res.checkSpecials = false;
res.version = version;
res.matchetype = matchetype;
res.internalRefs = internalRefs;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
res.dump("forSlicing");
return res;
}
public String getVersion() {
return version;
}
public ValidationContext setVersion(String version) {
this.version = version;
return this;
}
public Set<String> getInternalRefs() {
return internalRefs;
}
public boolean isMatchetype() {
return matchetype;
}
public void setMatchetype(boolean matchetype) {
this.matchetype = matchetype;
}
}

View File

@ -85,6 +85,7 @@ import org.hl7.fhir.validation.ValidatorUtils;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.InstanceValidator.MatchetypeStatus;
import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.AfterClass;
@ -371,6 +372,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (content.has("noHtmlInMarkdown")) {
val.setHtmlInMarkdownCheck(HtmlInMarkdownCheck.ERROR);
}
if (content.has("matchetype")) {
val.setMatchetypeStatus(MatchetypeStatus.Required);
}
List<String> suppress = new ArrayList<>();
if (content.has("suppress")) {
for (JsonElement c : content.getAsJsonArray("suppress")) {

View File

@ -43,6 +43,7 @@
"http://hl7.org/fhir/test/CodeSystem/imaginary|null" : null,
"http://loinc.org|2.74" : null,
"http://example.org/fhir/us/spl/CodeSystem/codesystem-organizationTypes|null" : null,
"http://hl7.org/fhir/test/CodeSystem/big|null" : null,
"Location|null" : null,
"http://terminology.hl7.org/CodeSystem/v3-NullFlavor|2018-08-12" : null,
"http://loinc.org|current" : null,

View File

@ -8,6 +8,7 @@
"http://hl7.org/fhir/us/qicore/ValueSet/qicore-negation-reason" : null,
"http://fhir.ch/ig/ch-ig/ValueSet/OrganizationType" : null,
"https://fhir.kbv.de/ValueSet/KBV_VS_SFHIR_ICD_SEITENLOKALISATION" : null,
"http://hl7.org/fhir/test/CodeSystem/big|null" : null,
"http://fhir.abda.de/eRezeptAbgabedaten/ValueSet/DAV-VS-ERP-DEUEV-Anlage-8" : null,
"http://loinc.org/vs/LL378-1" : null,
"http://something/something|null" : null,

View File

@ -23,7 +23,7 @@
<commons_io_version>2.17.0</commons_io_version>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.7.6</validator_test_case_version>
<validator_test_case_version>1.7.7-SNAPSHOT</validator_test_case_version>
<jackson_version>2.17.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>