Merge pull request #1486 from hapifhir/2023-11-gg-bundle-resolution-validation
2023 11 gg bundle resolution validation
This commit is contained in:
commit
85e8df42b7
|
@ -43,6 +43,7 @@ public class Constants {
|
|||
public final static String VERSION_MM = "5.0";
|
||||
public final static String DATE = "Thu, Mar 23, 2023 19:59+1100";
|
||||
public final static String URI_REGEX = "((http|https):\\/\\/([A-Za-z0-9\\\\\\.\\:\\%\\$\\-]*\\/)*?)?(Account|ActivityDefinition|ActorDefinition|AdministrableProductDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|ArtifactAssessment|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BiologicallyDerivedProductDispense|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|ChargeItem|ChargeItemDefinition|Citation|Claim|ClaimResponse|ClinicalImpression|ClinicalUseDefinition|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|ConditionDefinition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceAssociation|DeviceDefinition|DeviceDispense|DeviceMetric|DeviceRequest|DeviceUsage|DiagnosticReport|DocumentReference|Encounter|EncounterHistory|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceReport|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|FormularyItem|GenomicStudy|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingSelection|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|Ingredient|InsurancePlan|InventoryItem|InventoryReport|Invoice|Library|Linkage|List|Location|ManufacturedItemDefinition|Measure|MeasureReport|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProductDefinition|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionIntake|NutritionOrder|NutritionProduct|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|PackagedProductDefinition|Parameters|Patient|PaymentNotice|PaymentReconciliation|Permission|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RegulatedAuthorization|RelatedPerson|RequestOrchestration|Requirements|ResearchStudy|ResearchSubject|RiskAssessment|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|SubscriptionStatus|SubscriptionTopic|Substance|SubstanceDefinition|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestPlan|TestReport|TestScript|Transport|ValueSet|VerificationResult|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?";
|
||||
public final static String URI_REGEX_XVER = "((http|https):\\/\\/([A-Za-z0-9\\\\\\.\\:\\%\\$\\-]*\\/)*?)?($$)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?";
|
||||
public static final String NS_FHIR_ROOT = "http://hl7.org/fhir";
|
||||
public static final String NS_CDA_ROOT = "http://hl7.org/cda/stds/core";
|
||||
}
|
|
@ -91,6 +91,7 @@ import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMo
|
|||
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNodeList;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
|
||||
|
||||
public class StructureDefinitionRenderer extends ResourceRenderer {
|
||||
|
@ -3381,57 +3382,70 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
|
|||
}
|
||||
|
||||
public XhtmlNode compareMarkdown(String location, PrimitiveType md, PrimitiveType compare, int mode) throws FHIRException, IOException {
|
||||
XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div");
|
||||
if (compare == null || mode == GEN_MODE_DIFF) {
|
||||
if (md.hasValue()) {
|
||||
String xhtml = hostMd.processMarkdown(location, md);
|
||||
if (Utilities.noString(xhtml)) {
|
||||
return null;
|
||||
}
|
||||
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
|
||||
try {
|
||||
renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml));
|
||||
renderStatusDiv(md, ndiv).addChildren(fixFontSizes(new XhtmlParser().parseMDFragment(xhtml), 11));
|
||||
} catch (Exception e) {
|
||||
x.span("color: maroon").tx(e.getLocalizedMessage());
|
||||
ndiv.span("color: maroon").tx(e.getLocalizedMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return x;
|
||||
return ndiv;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (areEqual(compare, md)) {
|
||||
if (md.hasValue()) {
|
||||
String xhtml = "<div>"+hostMd.processMarkdown(location, md)+"</div>";
|
||||
XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
|
||||
for (XhtmlNode n : div.getChildNodes()) {
|
||||
String xhtml = hostMd.processMarkdown(location, md);
|
||||
List<XhtmlNode> nodes = new XhtmlParser().parseMDFragment(xhtml);
|
||||
for (XhtmlNode n : nodes) {
|
||||
if (n.getNodeType() == NodeType.Element) {
|
||||
n.style(unchangedStyle());
|
||||
}
|
||||
}
|
||||
return div;
|
||||
ndiv.addChildren(nodes);
|
||||
return ndiv;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div");
|
||||
if (md.hasValue()) {
|
||||
String xhtml = "<div>"+hostMd.processMarkdown(location, md)+"</div>";
|
||||
XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
|
||||
ndiv.copyAllContent(div);
|
||||
String xhtml = hostMd.processMarkdown(location, md);
|
||||
List<XhtmlNode> div = new XhtmlParser().parseMDFragment(xhtml);
|
||||
ndiv.addChildren(div);
|
||||
}
|
||||
if (compare.hasValue()) {
|
||||
String xhtml = "<div>"+hostMd.processMarkdown(location, compare)+"</div>";
|
||||
XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
|
||||
for (XhtmlNode n : div.getChildNodes()) {
|
||||
List<XhtmlNode> div = new XhtmlParser().parseMDFragment(xhtml);
|
||||
for (XhtmlNode n : div) {
|
||||
if (n.getNodeType() == NodeType.Element) {
|
||||
n.style(removedStyle());
|
||||
}
|
||||
}
|
||||
ndiv.br();
|
||||
ndiv.copyAllContent(div);
|
||||
ndiv.addChildren(div);
|
||||
}
|
||||
return ndiv;
|
||||
}
|
||||
}
|
||||
|
||||
private List<XhtmlNode> fixFontSizes(List<XhtmlNode> nodes, int size) {
|
||||
for (XhtmlNode x : nodes) {
|
||||
if (Utilities.existsInList(x.getName(), "p", "li") && !x.hasAttribute("style")) {
|
||||
x.style("font-size: "+size+"px");
|
||||
}
|
||||
if (x.hasChildren()) {
|
||||
fixFontSizes(x.getChildNodes(), size);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private boolean areEqual(PrimitiveType compare, PrimitiveType md) {
|
||||
if (compare == null && md == null) {
|
||||
return true;
|
||||
|
@ -3934,7 +3948,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
|
|||
list.merge(new DiscriminatorWithStatus(d));
|
||||
}
|
||||
}
|
||||
x.tx(", and can be differentiated using the following discriminators: ");
|
||||
var ul = x.ul();
|
||||
for (DiscriminatorWithStatus rc : list) {
|
||||
rc.render(x.li());
|
||||
|
@ -4316,7 +4329,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
|
|||
MarkdownType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement());
|
||||
if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) {
|
||||
bindingDesc = new XhtmlNode(NodeType.Element, "div");
|
||||
bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding)));
|
||||
bindingDesc.addChildren(new XhtmlParser().parseMDFragment(hostMd.processMarkdown("Binding.description", newBinding)));
|
||||
} else {
|
||||
|
||||
StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null;
|
||||
|
|
|
@ -28,6 +28,7 @@ public class I18nConstants {
|
|||
public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL = "Bundle_BUNDLE_Entry_NoProfile_EXPL";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE = "Bundle_BUNDLE_Entry_NoProfile_TYPE";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT = "BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT";
|
||||
public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT = "Bundle_BUNDLE_Entry_Orphan_DOCUMENT";
|
||||
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";
|
||||
|
@ -1022,6 +1023,9 @@ public class I18nConstants {
|
|||
public static final String FHIRPATH_ARITHMETIC_UNIT = "FHIRPATH_ARITHMETIC_UNIT";
|
||||
public static final String FHIRPATH_ARITHMETIC_PLUS = "FHIRPATH_ARITHMETIC_PLUS";
|
||||
public static final String FHIRPATH_ARITHMETIC_MINUS = "FHIRPATH_ARITHMETIC_MINUS";
|
||||
public static final String BUNDLE_ENTRY_URL_MATCHES_TYPE_ID = "BUNDLE_ENTRY_URL_MATCHES_TYPE_ID";
|
||||
public static final String BUNDLE_ENTRY_URL_MATCHES_NO_ID = "BUNDLE_ENTRY_URL_MATCHES_NO_ID";
|
||||
public static final String BUNDLE_ENTRY_URL_ABSOLUTE = "BUNDLE_ENTRY_URL_ABSOLUTE";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1285,6 +1285,11 @@ public class XhtmlParser {
|
|||
}
|
||||
}
|
||||
|
||||
public List<XhtmlNode> parseMDFragment(String source) throws IOException, FHIRException {
|
||||
XhtmlNode div = parseFragment( "<div>"+source+"</div>");
|
||||
return div.getChildNodes();
|
||||
}
|
||||
|
||||
public XhtmlNode parseFragment(String source) throws IOException, FHIRException {
|
||||
rdr = new StringReader(source);
|
||||
try {
|
||||
|
|
|
@ -12,6 +12,7 @@ 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_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_APPARENT = Can''t find ''{0}'' in the bundle ({1}). Note that there is a resource in the bundle with the same type and id, but it does not match because of the fullUrl based rules around matching relative resources
|
||||
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_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)
|
||||
|
@ -1079,3 +1080,6 @@ FHIRPATH_ARITHMETIC_QTY = Error in date arithmetic: attempt to add a definite qu
|
|||
FHIRPATH_ARITHMETIC_UNIT = Error in date arithmetic: unrecognized time unit {0}
|
||||
FHIRPATH_ARITHMETIC_PLUS = Error in date arithmetic: Unable to add type {0} to {1}
|
||||
FHIRPATH_ARITHMETIC_MINUS = Error in date arithmetic: Unable to subtract type {0} to {1}
|
||||
BUNDLE_ENTRY_URL_MATCHES_NO_ID = The fullUrl ''{0}'' looks like a RESTful server URL, but the resource has no id
|
||||
BUNDLE_ENTRY_URL_MATCHES_TYPE_ID = The fullUrl ''{0}'' looks like a RESTful server URL, so it must end with the correct type and id (/{1}/{2})
|
||||
BUNDLE_ENTRY_URL_ABSOLUTE = The fullUrl must be an absolute URL (not ''{0}'')
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
|||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.Constants;
|
||||
import org.hl7.fhir.r5.model.DomainResource;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
|
@ -68,14 +69,17 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
|
|||
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
|
||||
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader;
|
||||
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.StandardsStatus;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.validation.BaseValidator.ElementMatch;
|
||||
import org.hl7.fhir.validation.cli.utils.ValidationLevel;
|
||||
import org.hl7.fhir.validation.instance.utils.IndexedElement;
|
||||
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
||||
|
@ -83,6 +87,25 @@ import org.hl7.fhir.validation.instance.utils.NodeStack;
|
|||
public class BaseValidator implements IValidationContextResourceLoader {
|
||||
|
||||
|
||||
public class ElementMatch {
|
||||
|
||||
private Element element;
|
||||
private boolean valid;
|
||||
protected ElementMatch(Element element, boolean valid) {
|
||||
super();
|
||||
this.element = element;
|
||||
this.valid = valid;
|
||||
}
|
||||
public Element getElement() {
|
||||
return element;
|
||||
}
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class BooleanHolder {
|
||||
private boolean value = true;
|
||||
|
||||
|
@ -180,6 +203,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
this.xverManager = new XVerExtensionManager(context);
|
||||
}
|
||||
this.debug = debug;
|
||||
urlRegex = Constants.URI_REGEX_XVER.replace("$$", CommaSeparatedStringBuilder.join("|", context.getResourceNames()));
|
||||
}
|
||||
|
||||
public BaseValidator(BaseValidator parent) {
|
||||
|
@ -199,6 +223,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
this.warnOnDraftOrExperimental = parent.warnOnDraftOrExperimental;
|
||||
this.statusWarnings = parent.statusWarnings;
|
||||
this.bpWarnings = parent.bpWarnings;
|
||||
this.urlRegex = parent.urlRegex;
|
||||
}
|
||||
|
||||
private boolean doingLevel(IssueSeverity error) {
|
||||
|
@ -241,6 +266,8 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
*/
|
||||
private Map<String, ValidationControl> validationControl = new HashMap<>();
|
||||
|
||||
protected String urlRegex;
|
||||
|
||||
/**
|
||||
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
|
||||
*
|
||||
|
@ -998,12 +1025,15 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected Element resolveInBundle(Element bundle, List<Element> entries, String ref, String fullUrl, String type, String id) {
|
||||
protected ElementMatch resolveInBundle(Element bundle, List<Element> entries, String ref, String fullUrl, String type, String id) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Element> map = (Map<String, Element>) bundle.getUserData("validator.entrymap");
|
||||
Map<String, Element> relMap = (Map<String, Element>) bundle.getUserData("validator.entrymapR");
|
||||
if (map == null) {
|
||||
map = new HashMap<>();
|
||||
bundle.setUserData("validator.entrymap", map);
|
||||
relMap = new HashMap<>();
|
||||
bundle.setUserData("validator.entrymapR", relMap);
|
||||
for (Element entry : entries) {
|
||||
String fu = entry.getNamedChildValue(FULL_URL);
|
||||
map.put(fu, entry);
|
||||
|
@ -1011,14 +1041,25 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
if (resource != null) {
|
||||
String et = resource.getType();
|
||||
String eid = resource.getNamedChildValue(ID);
|
||||
if (eid != null) {
|
||||
if (VersionUtilities.isR4Plus(context.getVersion())) {
|
||||
relMap.put(et+"/"+eid, entry);
|
||||
} else {
|
||||
map.put(et+"/"+eid, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Utilities.isAbsoluteUrl(ref)) {
|
||||
// if the reference is absolute, then you resolve by fullUrl. No other thinking is required.
|
||||
return map.get(ref);
|
||||
Element e = map.get(ref);
|
||||
if (e == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ElementMatch(e, true);
|
||||
}
|
||||
// for (Element entry : entries) {
|
||||
// String fu = entry.getNamedChildValue(FULL_URL);
|
||||
// if (ref.equals(fu))
|
||||
|
@ -1028,9 +1069,10 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
} else {
|
||||
// split into base, type, and id
|
||||
String u = null;
|
||||
if (fullUrl != null && fullUrl.endsWith(type + "/" + id))
|
||||
if (fullUrl != null && fullUrl.matches(urlRegex) && fullUrl.endsWith(type + "/" + id)) {
|
||||
// fullUrl = complex
|
||||
u = fullUrl.substring(0, fullUrl.length() - (type + "/" + id).length()) + ref;
|
||||
}
|
||||
// u = fullUrl.substring((type+"/"+id).length())+ref;
|
||||
String[] parts = ref.split("\\/");
|
||||
if (parts.length >= 2) {
|
||||
|
@ -1040,7 +1082,14 @@ public class BaseValidator implements IValidationContextResourceLoader {
|
|||
if (res == null) {
|
||||
res = map.get(t+"/"+i);
|
||||
}
|
||||
return res;
|
||||
if (res == null && relMap.containsKey(t+"/"+i)) {
|
||||
res = relMap.get(t+"/"+i);
|
||||
return new ElementMatch(res, false);
|
||||
} else if (res == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ElementMatch(res, true);
|
||||
}
|
||||
// for (Element entry : entries) {
|
||||
// String fu = entry.getNamedChildValue(FULL_URL);
|
||||
// if (fu != null && fu.equals(u))
|
||||
|
|
|
@ -301,6 +301,19 @@ public class ValidatorCli {
|
|||
res.add("-bundle");
|
||||
res.add("Composition:0");
|
||||
res.add("http://hl7.org.au/fhir/ips/StructureDefinition/Composition-au-ips");
|
||||
} else if (a.equals("-ips:nz")) {
|
||||
res.add("-version");
|
||||
res.add("4.0");
|
||||
res.add("-check-ips-codes");
|
||||
res.add("-ig");
|
||||
res.add("tewhatuora.fhir.nzps#current");
|
||||
res.add("-profile");
|
||||
res.add("https://standards.digital.health.nz/fhir/StructureDefinition/nzps-bundle");
|
||||
res.add("-extension");
|
||||
res.add("any");
|
||||
res.add("-bundle");
|
||||
res.add("Composition:0");
|
||||
res.add("https://standards.digital.health.nz/fhir/StructureDefinition/nzps-composition");
|
||||
} else if (a.equals("-ips#")) {
|
||||
res.add("-version");
|
||||
res.add("4.0");
|
||||
|
|
|
@ -5381,9 +5381,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
if (!Utilities.noString(ref)) {
|
||||
for (Element bundle : bundles) {
|
||||
List<Element> entries = bundle.getChildren(ENTRY);
|
||||
Element tgt = resolveInBundle(bundle, entries, ref, fu, resource.fhirType(), resource.getIdBase());
|
||||
if (tgt != null) {
|
||||
element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE));
|
||||
ElementMatch tgt = resolveInBundle(bundle, entries, ref, fu, resource.fhirType(), resource.getIdBase());
|
||||
if (tgt != null && tgt.isValid()) {
|
||||
element.setUserData("validator.bundle.resolution", tgt.getElement().getNamedChild(RESOURCE));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public class BundleValidator extends BaseValidator {
|
|||
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;
|
||||
} else {
|
||||
// Get the first entry, the MessageHeader
|
||||
// Get the first entry, the MessageHeader or Document
|
||||
Element firstEntry = entries.get(0);
|
||||
// Get the stack of the first entry
|
||||
NodeStack firstStack = stack.push(firstEntry, 1, null, null);
|
||||
|
@ -75,16 +75,14 @@ public class BundleValidator extends BaseValidator {
|
|||
ok = handleSpecialCaseForLastUpdated(bundle, errors, stack) && ok;
|
||||
}
|
||||
ok = checkAllInterlinked(errors, entries, stack, bundle, false) && ok;
|
||||
}
|
||||
if (type.equals(MESSAGE)) {
|
||||
} else if (type.equals(MESSAGE)) {
|
||||
Element resource = firstEntry.getNamedChild(RESOURCE);
|
||||
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) {
|
||||
String id = resource.getNamedChildValue(ID);
|
||||
ok = validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id) && ok;
|
||||
ok = checkAllInterlinked(errors, entries, stack, bundle, true) && ok;
|
||||
}
|
||||
}
|
||||
if (type.equals(SEARCHSET)) {
|
||||
} else if (type.equals(SEARCHSET)) {
|
||||
ok = checkSearchSet(errors, bundle, entries, stack) && ok;
|
||||
}
|
||||
// We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles
|
||||
|
@ -101,8 +99,24 @@ public class BundleValidator extends BaseValidator {
|
|||
String fullUrl = entry.getNamedChildValue(FULL_URL);
|
||||
String url = getCanonicalURLForEntry(entry);
|
||||
String id = getIdForEntry(entry);
|
||||
String rtype = getTypeForEntry(entry);
|
||||
|
||||
if (!Utilities.noString(fullUrl)) {
|
||||
if (Utilities.isAbsoluteUrl(fullUrl)) {
|
||||
if (rtype != null && fullUrl.matches(urlRegex)) {
|
||||
if (rule(errors, "2023-11-13", IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), id != null, I18nConstants.BUNDLE_ENTRY_URL_MATCHES_NO_ID, fullUrl)) {
|
||||
ok = rule(errors, "2023-11-13", IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), fullUrl.endsWith("/"+rtype+"/"+id), I18nConstants.BUNDLE_ENTRY_URL_MATCHES_TYPE_ID, fullUrl, rtype, id) && ok;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
rule(errors, "2023-11-13", IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), false, I18nConstants.BUNDLE_ENTRY_URL_ABSOLUTE, fullUrl);
|
||||
}
|
||||
}
|
||||
if (url != null) {
|
||||
if (!(!url.equals(fullUrl) || (url.matches(uriRegexForVersion()) && url.endsWith("/" + id))) && !isV3orV2Url(url))
|
||||
if (!(!url.equals(fullUrl) || (url.matches(urlRegex) && url.endsWith("/" + id))) && !isV3orV2Url(url))
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MISMATCHIDURL, url, fullUrl, id) && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild(RESOURCE).fhirType(), id))), I18nConstants.BUNDLE_BUNDLE_ENTRY_CANONICAL, url, fullUrl) && ok;
|
||||
}
|
||||
|
@ -111,8 +125,7 @@ public class BundleValidator extends BaseValidator {
|
|||
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, entry.line(), entry.col(), estack.getLiteralPath(), fullUrlOptional || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED) && ok;
|
||||
}
|
||||
// check bundle profile requests
|
||||
if (entry.hasChild(RESOURCE)) {
|
||||
String rtype = entry.getNamedChild(RESOURCE).fhirType();
|
||||
if (rtype != null) {
|
||||
int rcount = counter.containsKey(rtype) ? counter.get(rtype)+1 : 0;
|
||||
counter.put(rtype, rcount);
|
||||
Element res = entry.getNamedChild(RESOURCE);
|
||||
|
@ -548,9 +561,12 @@ public class BundleValidator extends BaseValidator {
|
|||
}
|
||||
|
||||
if (ref != null && !Utilities.noString(reference) && !reference.startsWith("#")) {
|
||||
Element target = resolveInBundle(bundle, entries, reference, fullUrl, type, id);
|
||||
return rule(errors, NO_RULE_DATE, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target != null,
|
||||
I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND, reference, name);
|
||||
ElementMatch target = resolveInBundle(bundle, entries, reference, fullUrl, type, id);
|
||||
if (target == null) {
|
||||
return rule(errors, NO_RULE_DATE, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND, reference, name);
|
||||
} else {
|
||||
return rule(errors, NO_RULE_DATE, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target.isValid(), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT, reference, name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -594,9 +610,9 @@ public class BundleValidator extends BaseValidator {
|
|||
for (EntrySummary e : entryList) {
|
||||
Set<String> references = findReferences(e.getEntry());
|
||||
for (String ref : references) {
|
||||
Element tgt = resolveInBundle(bundle, entries, ref, e.getEntry().getChildValue(FULL_URL), e.getResource().fhirType(), e.getResource().getIdBase());
|
||||
if (tgt != null) {
|
||||
EntrySummary t = entryForTarget(entryList, tgt);
|
||||
ElementMatch tgt = resolveInBundle(bundle, entries, ref, e.getEntry().getChildValue(FULL_URL), e.getResource().fhirType(), e.getResource().getIdBase());
|
||||
if (tgt != null && tgt.isValid()) {
|
||||
EntrySummary t = entryForTarget(entryList, tgt.getElement());
|
||||
if (t != null ) {
|
||||
if (t != e) {
|
||||
// System.out.println("Entry "+e.getIndex()+" refers to "+t.getIndex()+" by ref '"+ref+"'");
|
||||
|
@ -689,13 +705,6 @@ public class BundleValidator extends BaseValidator {
|
|||
return Utilities.existsInList(fhirType, "Provenance");
|
||||
}
|
||||
|
||||
private String uriRegexForVersion() {
|
||||
if (VersionUtilities.isR3Ver(context.getVersion()))
|
||||
return URI_REGEX3;
|
||||
else
|
||||
return Constants.URI_REGEX;
|
||||
}
|
||||
|
||||
private String getCanonicalURLForEntry(Element entry) {
|
||||
Element e = entry.getNamedChild(RESOURCE);
|
||||
if (e == null)
|
||||
|
@ -710,6 +719,13 @@ public class BundleValidator extends BaseValidator {
|
|||
return e.getNamedChildValue(ID);
|
||||
}
|
||||
|
||||
private String getTypeForEntry(Element entry) {
|
||||
Element e = entry.getNamedChild(RESOURCE);
|
||||
if (e == null)
|
||||
return null;
|
||||
return e.fhirType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check each resource entry to ensure that the entry's fullURL includes the resource's id
|
||||
* value. Adds an ERROR ValidationMessge to errors List for a given entry if it references
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -20,7 +20,7 @@
|
|||
<properties>
|
||||
<guava_version>32.0.1-jre</guava_version>
|
||||
<hapi_fhir_version>6.4.1</hapi_fhir_version>
|
||||
<validator_test_case_version>1.4.16</validator_test_case_version>
|
||||
<validator_test_case_version>1.4.17-SNAPSHOT</validator_test_case_version>
|
||||
<jackson_version>2.15.2</jackson_version>
|
||||
<junit_jupiter_version>5.9.2</junit_jupiter_version>
|
||||
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>
|
||||
|
|
Loading…
Reference in New Issue