Merge pull request #1029 from hapifhir/gg-202212-json-header

Gg 202212 json header
This commit is contained in:
Grahame Grieve 2022-12-10 00:01:26 +11:00 committed by GitHub
commit 89486c17fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 244 additions and 17 deletions

View File

@ -186,6 +186,7 @@ public class ToolingExtensions {
public static final String EXT_RESOURCE_INTERFACE = "http://hl7.org/fhir/StructureDefinition/structuredefinition-interface"; public static final String EXT_RESOURCE_INTERFACE = "http://hl7.org/fhir/StructureDefinition/structuredefinition-interface";
public static final String EXT_SEC_CAT = "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category"; public static final String EXT_SEC_CAT = "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category";
public static final String EXT_STANDARDS_STATUS = "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status"; public static final String EXT_STANDARDS_STATUS = "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status";
public static final String EXT_STANDARDS_STATUS_REASON = "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason";
public static final String EXT_TABLE_NAME = "http://hl7.org/fhir/StructureDefinition/structuredefinition-table-name"; public static final String EXT_TABLE_NAME = "http://hl7.org/fhir/StructureDefinition/structuredefinition-table-name";
public static final String EXT_TARGET_ID = "http://hl7.org/fhir/StructureDefinition/targetElement"; public static final String EXT_TARGET_ID = "http://hl7.org/fhir/StructureDefinition/targetElement";
public static final String EXT_TARGET_PATH = "http://hl7.org/fhir/StructureDefinition/targetPath"; public static final String EXT_TARGET_PATH = "http://hl7.org/fhir/StructureDefinition/targetPath";
@ -837,6 +838,10 @@ public class ToolingExtensions {
return StandardsStatus.fromCode(ToolingExtensions.readStringExtension(dr, ToolingExtensions.EXT_STANDARDS_STATUS)); return StandardsStatus.fromCode(ToolingExtensions.readStringExtension(dr, ToolingExtensions.EXT_STANDARDS_STATUS));
} }
public static StandardsStatus getStandardsStatus(Element e) throws FHIRException {
return StandardsStatus.fromCode(ToolingExtensions.readStringExtension(e, ToolingExtensions.EXT_STANDARDS_STATUS));
}
public static void setStandardsStatus(DomainResource dr, StandardsStatus status, String normativeVersion) { public static void setStandardsStatus(DomainResource dr, StandardsStatus status, String normativeVersion) {
if (status == null) if (status == null)
ToolingExtensions.removeExtension(dr, ToolingExtensions.EXT_STANDARDS_STATUS); ToolingExtensions.removeExtension(dr, ToolingExtensions.EXT_STANDARDS_STATUS);

View File

@ -311,7 +311,12 @@ public class VersionUtilities {
return true; return true;
} }
if (pc!=null) { if (pc!=null) {
return compareVersionPart(pt, pc); if (pt.contains("-") && !pc.contains("-")) {
pt = pt.substring(0, pt.indexOf("-"));
return pt.compareTo(pc) >= 0;
} else {
return compareVersionPart(pt, pc);
}
} }
} }
return false; return false;

View File

@ -29,8 +29,11 @@ public class I18nConstants {
public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE = "Bundle_BUNDLE_Entry_NoProfile_TYPE"; public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE = "Bundle_BUNDLE_Entry_NoProfile_TYPE";
public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES"; public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES";
public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound"; public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound";
public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN = "Bundle_BUNDLE_Entry_Orphan"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT = "Bundle_BUNDLE_Entry_Orphan_DOCUMENT";
public static final String BUNDLE_BUNDLE_ENTRY_REVERSE = "BUNDLE_BUNDLE_ENTRY_REVERSE"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_MESSAGE = "Bundle_BUNDLE_Entry_Orphan_MESSAGE";
public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_R4 = "BUNDLE_BUNDLE_ENTRY_REVERSE_R4";
public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_R5 = "BUNDLE_BUNDLE_ENTRY_REVERSE_R5";
public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_MSG = "BUNDLE_BUNDLE_ENTRY_REVERSE_MSG";
public static final String BUNDLE_BUNDLE_ENTRY_TYPE = "Bundle_BUNDLE_Entry_Type"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE = "Bundle_BUNDLE_Entry_Type";
public static final String BUNDLE_BUNDLE_ENTRY_TYPE2 = "Bundle_BUNDLE_Entry_Type2"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE2 = "Bundle_BUNDLE_Entry_Type2";
public static final String BUNDLE_BUNDLE_ENTRY_TYPE3 = "Bundle_BUNDLE_Entry_Type3"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE3 = "Bundle_BUNDLE_Entry_Type3";
@ -38,6 +41,13 @@ public class I18nConstants {
public static final String BUNDLE_BUNDLE_FULLURL_NEEDVERSION = "Bundle_BUNDLE_FullUrl_NeedVersion"; public static final String BUNDLE_BUNDLE_FULLURL_NEEDVERSION = "Bundle_BUNDLE_FullUrl_NeedVersion";
public static final String BUNDLE_BUNDLE_MULTIPLEMATCHES = "Bundle_BUNDLE_MultipleMatches"; public static final String BUNDLE_BUNDLE_MULTIPLEMATCHES = "Bundle_BUNDLE_MultipleMatches";
public static final String BUNDLE_BUNDLE_NOT_LOCAL = "Bundle_BUNDLE_Not_Local"; public static final String BUNDLE_BUNDLE_NOT_LOCAL = "Bundle_BUNDLE_Not_Local";
public static final String BUNDLE_LINK_UNKNOWN = "BUNDLE_LINK_UNKNOWN";
public static final String BUNDLE_LINK_SEARCH_PROHIBITED = "BUNDLE_LINK_SEARCH_PROHIBITED";
public static final String BUNDLE_LINK_SEARCH_NO_DUPLICATES = "BUNDLE_LINK_SEARCH_NO_DUPLICATES";
public static final String BUNDLE_LINK_STYELSHEET_EXTERNAL = "BUNDLE_LINK_STYELSHEET_EXTERNAL";
public static final String BUNDLE_LINK_STYELSHEET_INSECURE = "BUNDLE_LINK_STYELSHEET_INSECURE";
public static final String BUNDLE_LINK_STYELSHEET_LINKABLE = "BUNDLE_LINK_STYELSHEET_LINKABLE";
public static final String BUNDLE_LINK_STYELSHEET_NOT_FOUND = "BUNDLE_LINK_STYELSHEET_NOT_FOUND";
public static final String BUNDLE_MSG_EVENT_COUNT = "Bundle_MSG_Event_Count"; public static final String BUNDLE_MSG_EVENT_COUNT = "Bundle_MSG_Event_Count";
public static final String CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_ = "Cant_have_children_on_an_element_with_a_polymorphic_type__you_must_slice_and_constrain_the_types_first_sortElements_"; public static final String CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_ = "Cant_have_children_on_an_element_with_a_polymorphic_type__you_must_slice_and_constrain_the_types_first_sortElements_";
public static final String CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT = "Can_only_specify_profile_in_the_context"; public static final String CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT = "Can_only_specify_profile_in_the_context";

View File

@ -653,6 +653,7 @@ public class JsonParser {
private static byte[] fetch(String source) throws IOException { private static byte[] fetch(String source) throws IOException {
SimpleHTTPClient fetcher = new SimpleHTTPClient(); SimpleHTTPClient fetcher = new SimpleHTTPClient();
fetcher.addHeader("Accept", "application/json, application/fhir+json");
HTTPResult res = fetcher.get(source+"?nocache=" + System.currentTimeMillis()); HTTPResult res = fetcher.get(source+"?nocache=" + System.currentTimeMillis());
res.checkThrowException(); res.checkThrowException();
return res.getContent(); return res.getContent();

View File

@ -12,8 +12,11 @@ Bundle_BUNDLE_Entry_NoProfile_TYPE = No profile found for {0} resource of type '
Bundle_BUNDLE_Entry_NoProfile_EXPL = Specified profile {2} not found for {0} resource of type ''{0}'' Bundle_BUNDLE_Entry_NoProfile_EXPL = Specified profile {2} not found for {0} resource of type ''{0}''
Bundle_BUNDLE_Entry_NO_LOGICAL_EXPL = Specified logical model {1} not found for resource ''Binary/{0}'' Bundle_BUNDLE_Entry_NO_LOGICAL_EXPL = Specified logical model {1} not found for resource ''Binary/{0}''
Bundle_BUNDLE_Entry_NotFound = Can''t find ''{0}'' in the bundle ({1}) Bundle_BUNDLE_Entry_NotFound = Can''t find ''{0}'' in the bundle ({1})
Bundle_BUNDLE_Entry_Orphan = Entry {0} isn''t reachable by traversing from first Bundle entry Bundle_BUNDLE_Entry_Orphan_MESSAGE = Entry {0} isn''t reachable by traversing links (forward or backward) from the MessageHeader, so its presence should be reviewed (is it needed to process the message?)
BUNDLE_BUNDLE_ENTRY_REVERSE = Entry {0} isn''t reachable by traversing forwards from first Bundle entry, and isn''t a resource type that is typically used that way - check this is not missed somewhere Bundle_BUNDLE_Entry_Orphan_DOCUMENT = Entry {0} isn''t reachable by traversing links (forward or backward) from the Composition
BUNDLE_BUNDLE_ENTRY_REVERSE_R4 = Entry {0} isn''t reachable by traversing forwards from the Composition. Only Provenance is approved to be used this way (R4 section 3.3.1)
BUNDLE_BUNDLE_ENTRY_REVERSE_R5 = Entry {0} isn''t reachable by traversing forwards from the Composition. Check whether this should be linked directly from the composition if it's a source of narrative content
BUNDLE_BUNDLE_ENTRY_REVERSE_MSG = Entry {0} isn''t reachable by traversing forwards from the MessageHeader. Check that this is meant to be included (needed to process the message)
Bundle_BUNDLE_Entry_Type = The type ''{0}'' is not valid - no resources allowed here (allowed = {1}) Bundle_BUNDLE_Entry_Type = The type ''{0}'' is not valid - no resources allowed here (allowed = {1})
Bundle_BUNDLE_Entry_Type2 = The type ''{0}'' is not valid - must be {1} (allowed = {2}) Bundle_BUNDLE_Entry_Type2 = The type ''{0}'' is not valid - must be {1} (allowed = {2})
Bundle_BUNDLE_Entry_Type3_one = The type ''{1}'' is not valid - must be of type {2} Bundle_BUNDLE_Entry_Type3_one = The type ''{1}'' is not valid - must be of type {2}
@ -23,7 +26,7 @@ Bundle_BUNDLE_FullUrl_NeedVersion = Entries matching fullURL {0} should declare
Bundle_BUNDLE_MultipleMatches = Multiple matches in bundle for reference {0} Bundle_BUNDLE_MultipleMatches = Multiple matches in bundle for reference {0}
Bundle_BUNDLE_Not_Local = URN reference is not locally contained within the bundle {0} Bundle_BUNDLE_Not_Local = URN reference is not locally contained within the bundle {0}
Bundle_MSG_Event_Count = Expected {0} but found {1} event elements Bundle_MSG_Event_Count = Expected {0} but found {1} event elements
Bundle_Document_Date_Missing = A document must have a date Bundle_Document_Date_Missing = A document must have a date (Bundle.timestamp)
Bundle_Document_Date_Missing_html = [(type = ''document'') implies (meta.lastUpdated.hasValue())] Bundle_Document_Date_Missing_html = [(type = ''document'') implies (meta.lastUpdated.hasValue())]
CapabalityStatement_CS_SP_WrongType = Type mismatch - SearchParameter ''{0}'' type is {1}, but type here is {2} CapabalityStatement_CS_SP_WrongType = Type mismatch - SearchParameter ''{0}'' type is {1}, but type here is {2}
CodeSystem_CS_VS_IncludeDetails = CodeSystem {0} has an ''all system'' value set of {1}, but the include has extra details CodeSystem_CS_VS_IncludeDetails = CodeSystem {0} has an ''all system'' value set of {1}, but the include has extra details
@ -807,3 +810,10 @@ JSON_PROPERTY_VALUE_NO_QUOTES = The JSON property ''{0}'' has no quotes around t
JSON_COMMA_MISSING = A Comma is missing in the JSON JSON_COMMA_MISSING = A Comma is missing in the JSON
JSON_COMMA_EXTRA = There is an extra comma at the end of the {0} in the JSON JSON_COMMA_EXTRA = There is an extra comma at the end of the {0} in the JSON
JSON_COMMENTS_NOT_ALLOWED = Comments are not allowed in JSON JSON_COMMENTS_NOT_ALLOWED = Comments are not allowed in JSON
BUNDLE_LINK_UNKNOWN = The link relationship type ''{0}'' is unknown and not allowed in this context
BUNDLE_LINK_SEARCH_PROHIBITED = The link relationship type ''{0}'' used in search sets is prohibited in this context
BUNDLE_LINK_SEARCH_NO_DUPLICATES = The link relationship type ''{0}'' can only occur once
BUNDLE_LINK_STYELSHEET_EXTERNAL = External Stylesheets other than https://hl7.org/fhir/fhir.css SHOULD not be used
BUNDLE_LINK_STYELSHEET_INSECURE = The stylesheet reference is not secure
BUNDLE_LINK_STYELSHEET_LINKABLE = The stylesheet reference is not a resolvable link
BUNDLE_LINK_STYELSHEET_NOT_FOUND = The stylesheet reference could not be resolved in this bundle

View File

@ -44,10 +44,19 @@ public class BundleValidator extends BaseValidator {
public boolean validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack, boolean checkSpecials, ValidatorHostContext hostContext, PercentageTracker pct, ValidationMode mode) { public boolean validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack, boolean checkSpecials, ValidatorHostContext hostContext, PercentageTracker pct, ValidationMode mode) {
boolean ok = true; boolean ok = true;
List<Element> entries = new ArrayList<Element>();
bundle.getNamedChildren(ENTRY, entries);
String type = bundle.getNamedChildValue(TYPE); String type = bundle.getNamedChildValue(TYPE);
type = StringUtils.defaultString(type); type = StringUtils.defaultString(type);
List<Element> entries = new ArrayList<Element>();
bundle.getNamedChildren(ENTRY, entries);
List<Element> links = new ArrayList<Element>();
bundle.getNamedChildren(LINK, links);
if (links.size() > 0) {
int i = 0;
for (Element l : links) {
ok = validateLink(errors, bundle, links, l, stack.push(l, i++, null, null), type, entries) && ok;
}
}
if (entries.size() == 0) { if (entries.size() == 0) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, stack.getLiteralPath(), !(type.equals(DOCUMENT) || type.equals(MESSAGE)), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST) && ok; ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, stack.getLiteralPath(), !(type.equals(DOCUMENT) || type.equals(MESSAGE)), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST) && ok;
@ -68,7 +77,7 @@ public class BundleValidator extends BaseValidator {
if (!VersionUtilities.isThisOrLater(FHIRVersion._4_0_1.getDisplay(), bundle.getProperty().getStructure().getFhirVersion().getDisplay())) { if (!VersionUtilities.isThisOrLater(FHIRVersion._4_0_1.getDisplay(), bundle.getProperty().getStructure().getFhirVersion().getDisplay())) {
ok = handleSpecialCaseForLastUpdated(bundle, errors, stack) && ok; ok = handleSpecialCaseForLastUpdated(bundle, errors, stack) && ok;
} }
ok = checkAllInterlinked(errors, entries, stack, bundle, true) && ok; ok = checkAllInterlinked(errors, entries, stack, bundle, false) && ok;
} }
if (type.equals(MESSAGE)) { if (type.equals(MESSAGE)) {
Element resource = firstEntry.getNamedChild(RESOURCE); Element resource = firstEntry.getNamedChild(RESOURCE);
@ -76,7 +85,7 @@ public class BundleValidator extends BaseValidator {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { if (rule(errors, NO_RULE_DATE, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) {
ok = validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id) && ok; ok = validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id) && ok;
} }
ok = checkAllInterlinked(errors, entries, stack, bundle, VersionUtilities.isR5Ver(context.getVersion())) && ok; ok = checkAllInterlinked(errors, entries, stack, bundle, true) && ok;
} }
if (type.equals(SEARCHSET)) { if (type.equals(SEARCHSET)) {
checkSearchSet(errors, bundle, entries, stack); checkSearchSet(errors, bundle, entries, stack);
@ -133,6 +142,151 @@ public class BundleValidator extends BaseValidator {
return ok; return ok;
} }
private boolean validateLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack, String type, List<Element> entries) {
switch (type) {
case "document": return validateDocumentLink(errors, bundle, links, link, stack, entries);
case "message": return validateMessageLink(errors, bundle, links, link, stack, entries);
case "history":
case "searchset": return validateSearchLink(errors, bundle, links, link, stack);
case "collection": return validateCollectionLink(errors, bundle, links, link, stack);
case "subscription-notification": return validateSubscriptionLink(errors, bundle, links, link, stack);
case "transaction":
case "transaction-response":
case "batch":
case "batch-response":
return validateTransactionOrBatchLink(errors, bundle, links, link, stack);
default:
return true; // unknown document type, deal with that elsewhere
}
// rule(errors, "2022-12-09", IssueType.INVALID, l.line(), l.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_UNKNOWN, );
}
private boolean validateDocumentLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack, List<Element> entries) {
boolean ok = true;
Element relE = link.getNamedChild("relation");
if (relE != null) {
NodeStack relStack = stack.push(relE, -1, null, null);
String rel = relE.getValue();
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel);
if ("self".equals(rel)) {
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok;
}
if ("stylesheet".equals(rel)) {
Element urlE = link.getNamedChild("url");
if (urlE != null) {
NodeStack urlStack = stack.push(urlE, -1, null, null);
String url = urlE.getValue();
if (url != null) {
if (Utilities.isAbsoluteUrl(url)) {
// todo: do we need to consider rel = base?
if (url.equals("https://hl7.org/fhir/fhir.css")) {
// well, this is ok!
} else {
warning(errors, "2022-12-09", IssueType.BUSINESSRULE, urlE.line(), urlE.col(), urlStack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_STYELSHEET_EXTERNAL);
if (url.startsWith("http://")) {
warning(errors, "2022-12-09", IssueType.BUSINESSRULE, urlE.line(), urlE.col(), urlStack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_STYELSHEET_INSECURE);
}
if (!Utilities.isAbsoluteUrlLinkable(url)) {
warning(errors, "2022-12-09", IssueType.BUSINESSRULE, urlE.line(), urlE.col(), urlStack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_STYELSHEET_LINKABLE);
}
}
} else {
// has to resolve in the bundle
boolean found = false;
for (Element e : entries) {
Element res = e.getNamedChild("resource");
if (res != null && (""+res.fhirType()+"/"+res.getIdBase()).equals(url)) {
found = true;
break;
}
}
ok = rule(errors, "2022-12-09", IssueType.NOTFOUND, urlE.line(), urlE.col(), urlStack.getLiteralPath(), found, I18nConstants.BUNDLE_LINK_STYELSHEET_NOT_FOUND) && ok;
}
}
}
}
}
return ok;
}
private boolean validateMessageLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack, List<Element> entries) {
boolean ok = true;
Element relE = link.getNamedChild("relation");
if (relE != null) {
NodeStack relStack = stack.push(relE, -1, null, null);
String rel = relE.getValue();
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel);
if ("self".equals(rel)) {
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok;
}
}
return ok;
}
private boolean validateSearchLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack) {
String rel = StringUtils.defaultString(link.getNamedChildValue("relation"));
if (Utilities.existsInList(rel, "first", "previous", "next", "last", "self")) {
return rule(errors, "2022-12-09", IssueType.INVALID, link.line(), link.col(), stack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel);
} else {
return true;
}
}
private boolean relationshipUnique(String rel, Element link, List<Element> links) {
for (Element l : links) {
if (l != link && rel.equals(l.getNamedChildValue("relation"))) {
return false;
}
if (l == link) {
// we only want to complain once, so we only look above this one
return true;
}
}
return true;
}
private boolean validateCollectionLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack) {
boolean ok = true;
Element relE = link.getNamedChild("relation");
if (relE != null) {
NodeStack relStack = stack.push(relE, -1, null, null);
String rel = relE.getValue();
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel);
if ("self".equals(rel)) {
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok;
}
}
return ok;
}
private boolean validateSubscriptionLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack) {
boolean ok = true;
Element relE = link.getNamedChild("relation");
if (relE != null) {
NodeStack relStack = stack.push(relE, -1, null, null);
String rel = relE.getValue();
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel);
if ("self".equals(rel)) {
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok;
}
}
return ok;
}
private boolean validateTransactionOrBatchLink(List<ValidationMessage> errors, Element bundle, List<Element> links, Element link, NodeStack stack) {
boolean ok = true;
Element relE = link.getNamedChild("relation");
if (relE != null) {
NodeStack relStack = stack.push(relE, -1, null, null);
String rel = relE.getValue();
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel);
if ("self".equals(rel)) {
ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok;
}
}
return ok;
}
private void checkSearchSet(List<ValidationMessage> errors, Element bundle, List<Element> entries, NodeStack stack) { private void checkSearchSet(List<ValidationMessage> errors, Element bundle, List<Element> entries, NodeStack stack) {
// warning: should have self link // warning: should have self link
List<Element> links = new ArrayList<Element>(); List<Element> links = new ArrayList<Element>();
@ -413,7 +567,7 @@ public class BundleValidator extends BaseValidator {
return ok; return ok;
} }
private boolean checkAllInterlinked(List<ValidationMessage> errors, List<Element> entries, NodeStack stack, Element bundle, boolean isError) { private boolean checkAllInterlinked(List<ValidationMessage> errors, List<Element> entries, NodeStack stack, Element bundle, boolean isMessage) {
boolean ok = true; boolean ok = true;
List<EntrySummary> entryList = new ArrayList<>(); List<EntrySummary> entryList = new ArrayList<>();
int i = 0; int i = 0;
@ -447,6 +601,7 @@ public class BundleValidator extends BaseValidator {
Set<EntrySummary> visited = new HashSet<>(); Set<EntrySummary> visited = new HashSet<>();
visitLinked(visited, entryList.get(0)); visitLinked(visited, entryList.get(0));
visitBundleLinks(visited, entryList, bundle);
boolean foundRevLinks; boolean foundRevLinks;
do { do {
foundRevLinks = false; foundRevLinks = false;
@ -460,10 +615,22 @@ public class BundleValidator extends BaseValidator {
} }
} }
if (add) { if (add) {
warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, e.getEntry().line(), e.getEntry().col(), if (isMessage) {
hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, e.getEntry().line(), e.getEntry().col(),
stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()),
I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE_MSG, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : ""));
} else {
// this was illegal up to R4B, but changed to be legal in R5
if (VersionUtilities.isR5VerOrLater(context.getVersion())) {
hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, e.getEntry().line(), e.getEntry().col(),
stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()),
I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE_R4, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : ""));
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.getEntry().line(), e.getEntry().col(),
stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()),
I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : "")); I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE_R4, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : ""));
// System.out.println("Found reverse links for "+e.getIndex()); }
}
foundRevLinks = true; foundRevLinks = true;
visitLinked(visited, e); visitLinked(visited, e);
} }
@ -474,10 +641,10 @@ public class BundleValidator extends BaseValidator {
i = 0; i = 0;
for (EntrySummary e : entryList) { for (EntrySummary e : entryList) {
Element entry = e.getEntry(); Element entry = e.getEntry();
if (isError) { if (isMessage) {
ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")) && ok; warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN_MESSAGE, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : ""));
} else { } else {
warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")); ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")) && ok;
} }
i++; i++;
} }
@ -486,6 +653,26 @@ public class BundleValidator extends BaseValidator {
private void visitBundleLinks(Set<EntrySummary> visited, List<EntrySummary> entryList, Element bundle) {
List<Element> links = bundle.getChildrenByName("link");
for (Element link : links) {
String rel = link.getNamedChildValue("relation");
String url = link.getNamedChildValue("url");
if (rel != null && url != null) {
if (Utilities.existsInList(rel, "stylesheet")) {
for (EntrySummary e : entryList) {
if (e.getResource() != null) {
if (url.equals(e.getResource().fhirType()+"/"+e.getResource().getIdBase())) {
visited.add(e);
break;
}
}
}
}
}
}
}
private boolean isExpectedToBeReverse(String fhirType) { private boolean isExpectedToBeReverse(String fhirType) {
return Utilities.existsInList(fhirType, "Provenance"); return Utilities.existsInList(fhirType, "Provenance");
} }

View File

@ -2068,3 +2068,12 @@ v: {
"system" : "http://snomed.info/sct" "system" : "http://snomed.info/sct"
} }
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------
{"code" : {
"code" : "text/css"
}, "url": "http://hl7.org/fhir/ValueSet/mimetypes", "version": "4.0.1", "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"true", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}####
v: {
"display" : "text/css",
"code" : "text/css",
"system" : "urn:ietf:bcp:13"
}
-------------------------------------------------------------------------------------