From 83dc198dd1eb450a4b4370586b2d798a6307ecba Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 11 Aug 2020 06:39:28 +1000 Subject: [PATCH 1/2] enforce case consistency when loading test cases directly --- .../org/hl7/fhir/utilities/tests/BaseTestingUtilities.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java index 3467c71eb..164daf0e3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java @@ -1,6 +1,7 @@ package org.hl7.fhir.utilities.tests; import org.apache.commons.io.IOUtils; +import org.hl7.fhir.utilities.CSFile; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; @@ -23,10 +24,10 @@ public class BaseTestingUtilities { * at the same directory level as the core project. */ String dir = System.getenv("FHIR-TEST-CASES"); - if (dir != null && new File(dir).exists()) { + if (dir != null && new CSFile(dir).exists()) { String n = Utilities.path(dir, Utilities.path(paths)); // ok, we'll resolve this locally - return TextFile.fileToString(new File(n)); + return TextFile.fileToString(new CSFile(n)); } else { // resolve from the package String contents; From 20a7682c9604aa932cb0a5f56df3eae4acf0f27f Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 11 Aug 2020 06:40:11 +1000 Subject: [PATCH 2/2] Add support for bundle profile parameter --- .../hl7/fhir/r5/utils/IResourceValidator.java | 34 ++++ .../fhir/utilities/i18n/I18nConstants.java | 7 + .../src/main/resources/Messages.properties | 159 +++++++++--------- .../hl7/fhir/validation/ValidationEngine.java | 8 +- .../fhir/validation/cli/model/CliContext.java | 10 ++ .../cli/services/ValidationService.java | 1 + .../hl7/fhir/validation/cli/utils/Params.java | 22 ++- .../instance/InstanceValidator.java | 8 +- .../instance/type/BundleValidator.java | 68 +++++++- .../validation/tests/ValidationTestSuite.java | 5 + 10 files changed, 234 insertions(+), 88 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java index 7b45a385a..03375b01a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java @@ -43,6 +43,8 @@ import org.hl7.fhir.exceptions.FHIRFormatError; 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.r5.utils.IResourceValidator.BundleValidationRule; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; import com.google.gson.JsonObject; @@ -56,6 +58,30 @@ import com.google.gson.JsonObject; */ public interface IResourceValidator { + public class BundleValidationRule { + private String rule; + private String profile; + private boolean checked; + + public BundleValidationRule(String rule, String profile) { + super(); + this.rule = rule; + this.profile = profile; + } + public String getRule() { + return rule; + } + public String getProfile() { + return profile; + } + public boolean isChecked() { + return checked; + } + public void setChecked(boolean checked) { + this.checked = checked; + } + } + public enum ReferenceValidationPolicy { IGNORE, CHECK_TYPE_IF_EXISTS, CHECK_EXISTS, CHECK_EXISTS_AND_TYPE, CHECK_VALID; @@ -189,6 +215,14 @@ public interface IResourceValidator { public void setCrumbTrails(boolean crumbTrails); + /** + * Bundle validation rules allow for requesting particular entries in a bundle get validated against particular profiles + * Typically this is used from the command line to avoid having to construct profile just to validate a particular resource + * in a bundle against a particular profile + * + * @return + */ + public List getBundleValidationRules(); /** * Validate suite * diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index c70ce1748..8f0fe4a0d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -543,6 +543,7 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_SIGNPOST = "VALIDATION_VAL_PROFILE_SIGNPOST"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META"; + public static final String VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM = "VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM"; public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder"; public static final String VALIDATION_VAL_PROFILE_OTHER_VERSION = "VALIDATION_VAL_PROFILE_OTHER_VERSION"; public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OK = "VALIDATION_VAL_PROFILE_THIS_VERSION_OK"; @@ -577,5 +578,11 @@ public class I18nConstants { public static final String _HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_ = "_has_children__and_multiple_types__in_profile_"; public static final String _HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE = "_has_children__for_type__in_profile__but_cant_find_type"; public static final String _HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_ = "_has_no_children__and_no_types_in_profile_"; + + public static final String BUNDLE_RULE_NONE = "BUNDLE_RULE_NONE"; + public static final String BUNDLE_RULE_UNKNOWN = "BUNDLE_RULE_UNKNOWN"; + public static final String BUNDLE_RULE_INVALID_INDEX = "BUNDLE_RULE_INVALID_INDEX"; + public static final String BUNDLE_RULE_PROFILE_UNKNOWN = "BUNDLE_RULE_PROFILE_UNKNOWN"; } + diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 243766cad..a7b5fc594 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1,18 +1,18 @@ #InstanceValidator -Bad_file_path_error = \n********************\n* The file name you passed in, "{0}", doesn't exist on the local filesystem.\n* Please verify that this is valid file location.\n********************\n\n +Bad_file_path_error = \n********************\n* The file name you passed in, '{0}', doesn't exist on the local filesystem.\n* Please verify that this is valid file location.\n********************\n\n Bundle_BUNDLE_Entry_Canonical = The canonical URL ({0}) cannot match the fullUrl ({1}) unless on the canonical server itself Bundle_BUNDLE_Entry_Document = The first entry in a document must be a composition -Bundle_BUNDLE_Entry_IdUrlMismatch = Resource ID does not match the ID in the entry full URL ("{0}" vs "{1}") +Bundle_BUNDLE_Entry_IdUrlMismatch = Resource ID does not match the ID in the entry full URL ('{0}' vs '{1}') Bundle_BUNDLE_Entry_MismatchIdUrl = The canonical URL ({0}) cannot match the fullUrl ({1}) unless the resource id ({2}) also matches Bundle_BUNDLE_Entry_NoFirst = Documents or Messages must contain at least one entry Bundle_BUNDLE_Entry_NoFirstResource = No resource on first entry Bundle_BUNDLE_Entry_NoFullUrl = Bundle entry missing fullUrl -Bundle_BUNDLE_Entry_NoProfile = No profile found for contained resource of type "{0}" -Bundle_BUNDLE_Entry_NotFound = Can''t find "{0}" in the bundle ({1}) +Bundle_BUNDLE_Entry_NoProfile = No profile found for contained resource of type '{0}' +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_Type = The type "{0}" is not valid - no resources allowed here -Bundle_BUNDLE_Entry_Type2 = The type "{0}" is not valid - must be {1} -Bundle_BUNDLE_Entry_Type3 = The type "{0}" is not valid - must be one of {1} +Bundle_BUNDLE_Entry_Type = The type '{0}' is not valid - no resources allowed here +Bundle_BUNDLE_Entry_Type2 = The type '{0}' is not valid - must be {1} +Bundle_BUNDLE_Entry_Type3 = The type '{0}' is not valid - must be one of {1} Bundle_BUNDLE_FullUrl_Missing = Relative Reference appears inside Bundle whose entry is missing a fullUrl Bundle_BUNDLE_FullUrl_NeedVersion = Entries matching fullURL {0} should declare meta/versionId because there are version-specific references Bundle_BUNDLE_MultipleMatches = Multiple matches in bundle for reference {0} @@ -20,7 +20,7 @@ Bundle_BUNDLE_Not_Local = URN reference is not locally contained within the bund 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_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_Invalid = CodeSystem {0} has an ''all system'' value set of {1}, but doesn''t have a single include CodeSystem_CS_VS_MisMatch = CodeSystem {0} has an ''all system'' value set of {1}, but it is an expansion @@ -31,19 +31,19 @@ Extension_EXT_Count_NotFound = Extension count mismatch: unable to find extensio Extension_EXT_Fixed_Banned = No extensions allowed, as the specified fixed value doesn''t contain any extensions Extension_EXT_Modifier_MismatchN = Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is Extension_EXT_Modifier_MismatchY = Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not -Extension_EXT_Modifier_N = The Extension "{0}" must not be used as an extension (it''s a modifierExtension) -Extension_EXT_Modifier_Y = The Extension "{0}" must be used as a modifierExtension -Extension_EXT_Simple = The Extension "{0}" definition is for a simple extension, so it must contain a value, not extensions -Extension_EXT_SubExtension_Invalid = Sub-extension url "{0}" is not defined by the Extension {1} -Extension_EXT_Type = The Extension "{0}" definition allows for the types {1} but found type {2} +Extension_EXT_Modifier_N = The Extension '{0}' must not be used as an extension (it''s a modifierExtension) +Extension_EXT_Modifier_Y = The Extension '{0}' must be used as a modifierExtension +Extension_EXT_Simple = The Extension '{0}' definition is for a simple extension, so it must contain a value, not extensions +Extension_EXT_SubExtension_Invalid = Sub-extension url '{0}' is not defined by the Extension {1} +Extension_EXT_Type = The Extension '{0}' definition allows for the types {1} but found type {2} Extension_EXT_URL_Absolute = Extension.url must be an absolute URL Extension_EXT_Unknown = Unknown extension {0} Extension_EXT_Unknown_NotHere = The extension {0} is unknown, and not allowed here Extension_EXT_Url_NotFound = Extension.url is required -Extension_EXT_Version_Internal = Extension url "{0}" evaluation state illegal -Extension_EXT_Version_Invalid = Extension url "{0}" is not valid (invalid Version "{1}") -Extension_EXT_Version_InvalidId = Extension url "{0}" is not valid (invalid Element id "{1}") -Extension_EXT_Version_NoChange = Extension url "{0}" is not valid (Element id "{1}" is valid, but cannot be used in a cross-version paradigm because there has been no changes across the relevant versions) +Extension_EXT_Version_Internal = Extension url '{0}' evaluation state illegal +Extension_EXT_Version_Invalid = Extension url '{0}' is not valid (invalid Version '{1}') +Extension_EXT_Version_InvalidId = Extension url '{0}' is not valid (invalid Element id '{1}') +Extension_EXT_Version_NoChange = Extension url '{0}' is not valid (Element id '{1}' is valid, but cannot be used in a cross-version paradigm because there has been no changes across the relevant versions) Fixed_Type_Checks_DT_Address_Line = Expected {0} but found {1} line elements Fixed_Type_Checks_DT_Name_Family = Expected {0} but found {1} family elements Fixed_Type_Checks_DT_Name_Given = Expected {0} but found {1} given elements @@ -57,11 +57,11 @@ Language_XHTML_Lang_Missing2 = Resource has a language, but the XHTML does not h Language_XHTML_Lang_Missing3 = Resource has a language, but the XHTML does not have an xml:lang tag (needs both lang and xml:lang - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues) Meta_RES_Security_Duplicate = Duplicate Security Label {0} MustSupport_VAL_MustSupport = The element {0} is not marked as ''mustSupport'' in the profile {1}. Consider not using the element, or marking the element as must-Support in the profile -Profile_EXT_Not_Here = The extension {0} is not allowed to be used at this point (based on context invariant "{1}") -Profile_VAL_MissingElement = Missing element "{0}" - required by fixed value assigned in profile {1} +Profile_EXT_Not_Here = The extension {0} is not allowed to be used at this point (based on context invariant '{1}') +Profile_VAL_MissingElement = Missing element '{0}' - required by fixed value assigned in profile {1} Profile_VAL_NotAllowed = The element {0} is present in the instance but not allowed in the applicable {1} specified in profile Measure_MR_M_None = No Measure is identified, so no validation can be performed against the Measure -Measure_MR_M_NotFound = The Measure "{0}" could not be resolved, so no validation can be performed against the Measure +Measure_MR_M_NotFound = The Measure '{0}' could not be resolved, so no validation can be performed against the Measure Questionnaire_QR_Item_BadOption = The value provided ({0}::{1}) is not in the options value set in the questionnaire Questionnaire_QR_Item_Coding = Error {0} validating Coding against Questionnaire Options Questionnaire_QR_Item_CodingNoOptions = Cannot validate Coding option because no option list is provided @@ -70,7 +70,7 @@ Questionnaire_QR_Item_Display = Items not of type DISPLAY should not have items Questionnaire_QR_Item_Group = Items of type group should not have answers Questionnaire_QR_Item_GroupAnswer = Items not of type group should not have items outside answers (use answer.item not .item) Questionnaire_QR_Item_IntNoOptions = Cannot validate integer answer option because no option list is provided -Questionnaire_QR_Item_Missing = No response answer found for required item "{0}" +Questionnaire_QR_Item_Missing = No response answer found for required item '{0}' Questionnaire_QR_Item_NoCoding = The code {0}::{1} is not a valid option Questionnaire_QR_Item_NoDate = The date {0} is not a valid option Questionnaire_QR_Item_NoInteger = The integer {0} is not a valid option @@ -85,8 +85,8 @@ Questionnaire_QR_Item_NoString = The string {0} is not a valid option Questionnaire_QR_Item_NoTime = The time {0} is not a valid option Questionnaire_QR_Item_NoType = Definition for item {0} does not contain a type Questionnaire_QR_Item_NotEnabled = Item has answer (2), even though it is not enabled {0} -Questionnaire_QR_Item_NotEnabled2 = Item has answer, even though it is not enabled (item id = "{0}") -Questionnaire_QR_Item_NotFound = LinkId "{0}" not found in questionnaire +Questionnaire_QR_Item_NotEnabled2 = Item has answer, even though it is not enabled (item id = '{0}') +Questionnaire_QR_Item_NotFound = LinkId '{0}' not found in questionnaire Questionnaire_QR_Item_OnlyOneA = Only one response answer item with this linkId allowed Questionnaire_QR_Item_OnlyOneI = Only one response item with this linkId allowed - {0} Questionnaire_QR_Item_Order = Structural Error: items are out of order @@ -96,19 +96,19 @@ Questionnaire_QR_Item_TimeNoOptions = Cannot validate time answer option because Questionnaire_QR_Item_WrongType = Answer value must be of type {0} Questionnaire_QR_Item_WrongType2 = Answer value must be one of the types {0} Questionnaire_QR_Q_None = No questionnaire is identified, so no validation can be performed against the base questionnaire -Questionnaire_QR_Q_NotFound = The questionnaire "{0}" could not be resolved, so no validation can be performed against the base questionnaire +Questionnaire_QR_Q_NotFound = The questionnaire '{0}' could not be resolved, so no validation can be performed against the base questionnaire Questionnaire_Q_EnableWhen_After = The target of this enableWhen rule ({0}) comes after the question itself Questionnaire_Q_EnableWhen_IsInner = Questions with an enableWhen cannot refer to an inner question for it''s enableWhen condition Questionnaire_Q_EnableWhen_NoLink = Questions with an enableWhen must have a value for the question link -Questionnaire_Q_EnableWhen_NoTarget = Unable to find an item with the linkId "{0}" which is referenced in the enableWhen for "{1}" +Questionnaire_Q_EnableWhen_NoTarget = Unable to find an item with the linkId '{0}' which is referenced in the enableWhen for '{1}' Questionnaire_Q_EnableWhen_Self = Target for this question enableWhen can''t reference itself Reference_REF_Aggregation = Reference is {0} which isn''t supported by the specified aggregation mode(s) for the reference Reference_REF_BadTargetType = Invalid Resource target type. Found {0}, but expected one of ({1}) -Reference_REF_BadTargetType2 = The type "{0}" implied by the reference URL {1} is not a valid Target for this element (must be one of {2}) +Reference_REF_BadTargetType2 = The type '{0}' implied by the reference URL {1} is not a valid Target for this element (must be one of {2}) Reference_REF_CantMatchChoice = Unable to find matching profile for {0} among choices: {1} Reference_REF_CantMatchType = Unable to find matching profile for {0} (by type) among choices: {1} -Reference_REF_CantResolve = Unable to resolve resource "{0}" -Reference_REF_CantResolveProfile = Unable to resolve the profile reference "{0}" +Reference_REF_CantResolve = Unable to resolve resource '{0}' +Reference_REF_CantResolveProfile = Unable to resolve the profile reference '{0}' Reference_REF_Format1 = Relative URLs must be of the format [ResourceName]/[id], or a search URL is allowed ([type]?parameters. Encountered {0}) Reference_REF_Format2 = Relative URLs must be of the format [ResourceName]/[id]. Encountered {0} Reference_REF_MultipleMatches = Found multiple matching profiles for {0} among choices: {1} @@ -116,10 +116,10 @@ Reference_REF_NoDisplay = A Reference without an actual reference or identifier Reference_REF_NoType = Unable to determine type of target resource Reference_REF_NotFound_Bundle = Bundled or contained reference not found within the bundle/resource {0} Reference_REF_ResourceType = Matching reference for reference {0} has resourceType {1} -Reference_REF_WrongTarget = The type "{0}" is not a valid Target for this element (must be one of {1}) +Reference_REF_WrongTarget = The type '{0}' is not a valid Target for this element (must be one of {1}) Resource_RES_ID_Missing = Resource requires an id, but none is present Resource_RES_ID_Prohibited = Resource has an id, but none is allowed -Terminology_PassThrough_TX_Message = {0} for "{1}#{2}" +Terminology_PassThrough_TX_Message = {0} for '{1}#{2}' Terminology_TX_Binding_CantCheck = Binding by URI reference cannot be checked Terminology_TX_Binding_Missing = Binding for {0} missing (cc) Terminology_TX_Binding_Missing2 = Binding for {0} missing @@ -138,7 +138,7 @@ Terminology_TX_Confirm_3 = Could not confirm that the codes provided are in the Terminology_TX_Confirm_4 = Could not confirm that the codes provided are in the value set {0}, and a code from this value set is required Terminology_TX_Confirm_5 = Could not confirm that the codes provided are in the value set {0}, and a code should come from this value set unless it has no suitable code Terminology_TX_Confirm_6 = Could not confirm that the codes provided are in the value set {0}, and a code is recommended to come from this value set -Terminology_TX_Display_Wrong = Display should be "{0}" +Terminology_TX_Display_Wrong = Display should be '{0}' Terminology_TX_Error_CodeableConcept = Error {0} validating CodeableConcept Terminology_TX_Error_CodeableConcept_Max = Error {0} validating CodeableConcept using maxValueSet Terminology_TX_Error_Coding1 = Error {0} validating Coding @@ -149,10 +149,10 @@ Terminology_TX_NoValid_11 = The code provided is not in the maximum value set {0 Terminology_TX_NoValid_12 = The Coding provided is not in the value set {0}, and a code is required from this value set. {1} Terminology_TX_NoValid_13 = The Coding provided ({2}) is not in the value set {0}, and a code should come from this value set unless it has no suitable code. {1} Terminology_TX_NoValid_14 = The Coding provided is not in the value set {0}, and a code is recommended to come from this value set. {1} -Terminology_TX_NoValid_15 = The value provided ("{0}") could not be validated in the absence of a terminology server -Terminology_TX_NoValid_16 = The value provided ("{0}") is not in the value set {1} ({2}), and a code is required from this value set){3} -Terminology_TX_NoValid_17 = The value provided ("{0}") is not in the value set {1} ({2}), and a code should come from this value set unless it has no suitable code){3} -Terminology_TX_NoValid_18 = The value provided ("{0}") is not in the value set {1} ({2}), and a code is recommended to come from this value set){3} +Terminology_TX_NoValid_15 = The value provided ('{0}') could not be validated in the absence of a terminology server +Terminology_TX_NoValid_16 = The value provided ('{0}') is not in the value set {1} ({2}), and a code is required from this value set){3} +Terminology_TX_NoValid_17 = The value provided ('{0}') is not in the value set {1} ({2}), and a code should come from this value set unless it has no suitable code){3} +Terminology_TX_NoValid_18 = The value provided ('{0}') is not in the value set {1} ({2}), and a code is recommended to come from this value set){3} Terminology_TX_NoValid_2 = None of the codes provided are in the value set {0} ({1}), and a code should come from this value set unless it has no suitable code) (codes = {2}) Terminology_TX_NoValid_3 = None of the codes provided are in the value set {0} ({1}), and a code is recommended to come from this value set) (codes = {2}) Terminology_TX_NoValid_4 = The Coding provided is not in the value set {0}, and a code is required from this value set{1} @@ -162,37 +162,37 @@ Terminology_TX_NoValid_7 = None of the codes provided could be validated against Terminology_TX_NoValid_8 = None of the codes provided are in the maximum value set {0} ({1}), and a code from this value set is required) (codes = {2}) Terminology_TX_NoValid_9 = The code provided could not be validated against the maximum value set {0} ({1}), (error = {2}) Terminology_TX_System_Invalid = Invalid System URI: {0} -Terminology_TX_System_NotKnown = Code System URI "{0}" is unknown so the code cannot be validated +Terminology_TX_System_NotKnown = Code System URI '{0}' is unknown so the code cannot be validated Terminology_TX_System_Relative = Coding.system must be an absolute reference, not a local reference -Terminology_TX_System_Unknown = Unknown Code System "{0}" +Terminology_TX_System_Unknown = Unknown Code System '{0}' Terminology_TX_System_ValueSet = Invalid System URI: {0} - cannot use a value set URI as a system -Terminology_TX_System_ValueSet2 = The Coding references a value set, not a code system ("{0}") +Terminology_TX_System_ValueSet2 = The Coding references a value set, not a code system ('{0}') Terminology_TX_ValueSet_NotFound = ValueSet {0} not found by validator Terminology_TX_ValueSet_NotFound2 = ValueSet {0} not found by validator -Type_Specific_Checks_DT_Base64_Valid = The value "{0}" is not a valid Base64 value +Type_Specific_Checks_DT_Base64_Valid = The value '{0}' is not a valid Base64 value Type_Specific_Checks_DT_Boolean_Value = boolean values must be ''true'' or ''false'' -Type_Specific_Checks_DT_Code_WS = The code "{0}" is not valid (whitespace rules) -Type_Specific_Checks_DT_DateTime_Reasonable = The value "{0}" is outside the range of reasonable years - check for data entry error -Type_Specific_Checks_DT_DateTime_Regex = The instant "{0}" is not valid (by regex) +Type_Specific_Checks_DT_Code_WS = The code '{0}' is not valid (whitespace rules) +Type_Specific_Checks_DT_DateTime_Reasonable = The value '{0}' is outside the range of reasonable years - check for data entry error +Type_Specific_Checks_DT_DateTime_Regex = The instant '{0}' is not valid (by regex) Type_Specific_Checks_DT_DateTime_TZ = if a date has a time, it must have a timezone Type_Specific_Checks_DT_DateTime_Valid = Not a valid date/time ({0}) Type_Specific_Checks_DT_Date_Valid = Not a valid date ({0}) -Type_Specific_Checks_DT_Decimal_Range = The value "{0}" is outside the range of commonly/reasonably supported decimals -Type_Specific_Checks_DT_Decimal_Valid = The value "{0}" is not a valid decimal -Type_Specific_Checks_DT_ID_Valid = id value "{0}" is not valid +Type_Specific_Checks_DT_Decimal_Range = The value '{0}' is outside the range of commonly/reasonably supported decimals +Type_Specific_Checks_DT_Decimal_Valid = The value '{0}' is not a valid decimal +Type_Specific_Checks_DT_ID_Valid = id value '{0}' is not valid Type_Specific_Checks_DT_Identifier_System = Identifier.system must be an absolute reference, not a local reference Type_Specific_Checks_DT_Instant_Valid = Not a valid instant ({0}) -Type_Specific_Checks_DT_Integer64_Valid = The value "{0}" is not a valid integer64 +Type_Specific_Checks_DT_Integer64_Valid = The value '{0}' is not a valid integer64 Type_Specific_Checks_DT_Integer_GT = value is greater than permitted maximum value of {0} Type_Specific_Checks_DT_Integer_LT = value is less than permitted minimum value of {0} Type_Specific_Checks_DT_Integer_LT0 = value is less than permitted minimum value of 0 Type_Specific_Checks_DT_Integer_LT1 = value is less than permitted minimum value of 1 -Type_Specific_Checks_DT_Integer_Valid = The value "{0}" is not a valid integer +Type_Specific_Checks_DT_Integer_Valid = The value '{0}' is not a valid integer Type_Specific_Checks_DT_OID_Start = OIDs must start with urn:oid: Type_Specific_Checks_DT_OID_Valid = OIDs must be valid Type_Specific_Checks_DT_Primitive_Length = value is longer than permitted maximum length of {0} Type_Specific_Checks_DT_Primitive_NotEmpty = @value cannot be empty -Type_Specific_Checks_DT_Primitive_Regex = Element value "{0}" does not meet regex "{1}" +Type_Specific_Checks_DT_Primitive_Regex = Element value '{0}' does not meet regex '{1}' Type_Specific_Checks_DT_Primitive_ValueExt = Primitive types must have a value or must have child extensions Type_Specific_Checks_DT_Primitive_WS = Primitive types should not only be whitespace Type_Specific_Checks_DT_String_Length = value is longer than permitted maximum length of 1 MB (1048576 bytes) @@ -200,8 +200,8 @@ Type_Specific_Checks_DT_String_WS = value should not start or finish with whites Type_Specific_Checks_DT_Time_Valid = Not a valid time ({0}) Type_Specific_Checks_DT_URI_OID = URI values cannot start with oid: Type_Specific_Checks_DT_URI_UUID = URI values cannot start with uuid: -Type_Specific_Checks_DT_URI_WS = URI values cannot have whitespace("{0}") -Type_Specific_Checks_DT_URL_Resolve = URL value "{0}" does not resolve +Type_Specific_Checks_DT_URI_WS = URI values cannot have whitespace('{0}') +Type_Specific_Checks_DT_URL_Resolve = URL value '{0}' does not resolve Type_Specific_Checks_DT_UUID_Strat = UUIDs must start with urn:uuid: Type_Specific_Checks_DT_UUID_Vaid = UUIDs must be valid ({0}) Validation_BUNDLE_Message = The first entry in a message must be a MessageHeader @@ -212,30 +212,30 @@ Validation_VAL_Profile_Maximum = {0}: max allowed = {1}, but found {2} Validation_VAL_Profile_Minimum = {0}: minimum required = {1}, but only found {2} Validation_VAL_Profile_MultipleMatches = Found multiple matching profiles among choices: {0} Validation_VAL_Profile_NoCheckMax = {0}: Unable to check max allowed ({1}) due to lack of slicing validation -Validation_VAL_Profile_NoCheckMin = {0}": Unable to check minimum required ({1}) due to lack of slicing validation -Validation_VAL_Profile_NoDefinition = No definition found for resource type "{0}" +Validation_VAL_Profile_NoCheckMin = {0}': Unable to check minimum required ({1}) due to lack of slicing validation +Validation_VAL_Profile_NoDefinition = No definition found for resource type '{0}' Validation_VAL_Profile_NoMatch = Unable to find matching profile among choices: {0} Validation_VAL_Profile_NoSnapshot = StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided Validation_VAL_Profile_NoType = The type of element {0} is not known, which is illegal. Valid types at this point are {1} Validation_VAL_Profile_NotAllowed = This element is not allowed by the profile {0} Validation_VAL_Profile_NotSlice = This element does not match any known slice {0} and slicing is CLOSED: {1} -Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element "{1}" is out of order -Validation_VAL_Profile_SliceOrder = As specified by profile {0}, Element "{1}" is out of order in ordered slice -Validation_VAL_Profile_Unknown = Profile reference "{0}" could not be resolved, so has not been checked -Validation_VAL_Profile_WrongType = Specified profile type was "{0}", but found type "{1}" +Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element '{1}' is out of order +Validation_VAL_Profile_SliceOrder = As specified by profile {0}, Element '{1}' is out of order in ordered slice +Validation_VAL_Profile_Unknown = Profile reference '{0}' could not be resolved, so has not been checked +Validation_VAL_Profile_WrongType = Specified profile type was '{0}', but found type '{1}' Validation_VAL_Unknown_Profile = Unknown profile {0} -XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML ("{0}" on "{1}") -XHTML_XHTML_Element_Illegal = Illegal element name in the XHTML ("{0}") -XHTML_XHTML_NS_InValid = Wrong namespace on the XHTML ("{0}", should be "{1}") -XHTML_XHTML_Name_Invalid = Wrong name on the XHTML ("{0}") - must start with div -_DT_Fixed_Wrong = Value is "{0}" but must be "{1}" +XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML ('{0}' on '{1}') +XHTML_XHTML_Element_Illegal = Illegal element name in the XHTML ('{0}') +XHTML_XHTML_NS_InValid = Wrong namespace on the XHTML ('{0}', should be '{1}') +XHTML_XHTML_Name_Invalid = Wrong name on the XHTML ('{0}') - must start with div +_DT_Fixed_Wrong = Value is '{0}' but must be '{1}' All_observations_should_have_an_effectiveDateTime_or_an_effectivePeriod = All observations should have an effectiveDateTime or an effectivePeriod All_observations_should_have_a_performer = All observations should have a performer All_observations_should_have_a_subject = All observations should have a subject Unable_to_resolve_slice_matching__no_fixed_value_or_required_value_set = Unable to resolve slice matching - no fixed value or required value set Unable_to_resolve_slice_matching__slice_matching_by_value_set_not_done = Unable to resolve slice matching - slice matching by value set not done Problem_processing_expression__in_profile__path__ = Problem processing expression {0} in profile {1} path {2}: {3} -Unable_to_find_element_with_id_ = Unable to find element with id "{0}" +Unable_to_find_element_with_id_ = Unable to find element with id '{0}' Slice_encountered_midway_through_set_path___id___ = Slice encountered midway through set (path = {0}, id = {1}); {2} Unable_to_resolve_actual_type_ = Unable to resolve actual type {0} Unsupported_version_R1 = Unsupported version R1 @@ -264,7 +264,7 @@ Unable_to_resolve_element__in_profile_ = Unable to resolve element {0} in profil Unable_to_resolve_profile_ = Unable to resolve profile {0} Resource_resolution_services_not_provided = Resource resolution services not provided Unrecognised_extension_context_ = Unrecognised extension context {0} -Unable_to_locate_the_profile__in_order_to_validate_against_it = Unable to locate the profile "{0}" in order to validate against it +Unable_to_locate_the_profile__in_order_to_validate_against_it = Unable to locate the profile '{0}' in order to validate against it Reference__refers_to_a__not_a_ValueSet = Reference {0} refers to a {1} not a ValueSet Not_done_yet_ValidatorHostServicesconformsToProfile_when_item_is_not_an_element = Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element Not_supported_yet = Not supported yet @@ -349,14 +349,14 @@ element__null_ = element = null: {0} getSliceList_should_only_be_called_when_the_element_has_slicing = getSliceList should only be called when the element has slicing Unable_to_resolve_name_reference__at_path_ = Unable to resolve name reference {0} at path {1} Details_for__matching_against_Profile_ = Details for {0} matching against Profile{1} -Does_not_match_slice_ = Does not match slice "{0}" +Does_not_match_slice_ = Does not match slice '{0}' Profile__does_not_match_for__because_of_the_following_profile_issues__ = Profile {0} does not match for {1} because of the following profile issues: {2} This_element_does_not_match_any_known_slice_ = This element does not match any known slice{0} defined_in_the_profile = defined in the profile -This_does_not_appear_to_be_a_FHIR_resource_unknown_name_ = This does not appear to be a FHIR resource (unknown name "{0}") +This_does_not_appear_to_be_a_FHIR_resource_unknown_name_ = This does not appear to be a FHIR resource (unknown name '{0}') This_cannot_be_parsed_as_a_FHIR_object_no_name = This cannot be parsed as a FHIR object (no name) -This_does_not_appear_to_be_a_FHIR_resource_unknown_namespacename_ = This does not appear to be a FHIR resource (unknown namespace/name "{0}::{1}") -This__cannot_be_parsed_as_a_FHIR_object_no_namespace = This "{0}" cannot be parsed as a FHIR object (no namespace) +This_does_not_appear_to_be_a_FHIR_resource_unknown_namespacename_ = This does not appear to be a FHIR resource (unknown namespace/name '{0}::{1}') +This__cannot_be_parsed_as_a_FHIR_object_no_namespace = This '{0}' cannot be parsed as a FHIR object (no namespace) Unable_to_find_resourceType_property = Unable to find resourceType property Error_parsing_JSON_the_primitive_value_must_be_a_string = Error parsing JSON: the primitive value must be a string Error_parsing_JSON_the_primitive_value_must_be_a_number = Error parsing JSON: the primitive value must be a number @@ -437,7 +437,7 @@ This_property_must_be_an_Array_not_a_ = This property must be an Array, not a {0 documentmsg = (document) xml_attr_value_invalid = The XML Attribute {0} has an illegal character xml_encoding_invalid = The XML encoding is invalid (must be UTF-8) -xml_stated_encoding_invalid = The XML encoding stated in the header is invalid (must be "UTF-8" if stated) +xml_stated_encoding_invalid = The XML encoding stated in the header is invalid (must be 'UTF-8' if stated) XHTML_URL_INVALID = The URL {0} is not valid ({1}) MEASURE_MR_GRP_NO_CODE = Group should have a code that matches the group definition in the measure MEASURE_MR_GRP_UNK_CODE = The code for this group has no match in the measure definition @@ -496,7 +496,7 @@ TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG = Attachment size is {0} bytes which exceed TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT = Attachments have data and/or url, or else SHOULD have either contentType and/or language TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG = Base64 size is {0} bytes which exceeds the stated limit of {1} bytes TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = Found {0} decimal places which exceeds the stated limit of {1} digits -Validation_VAL_Profile_WrongType = Specified profile type was "{0}", but found type "{1}" +Validation_VAL_Profile_WrongType = Specified profile type was '{0}', but found type '{1}' Validation_VAL_Profile_WrongType2 = Type mismatch processing profile {0} at path {1}: The element type is {4}, but the profile {3} is for a different type {2} VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT = Illegal constraint in profile {0} at path {1} - cannot constrain to type {2} from base types {3} EXTENSION_EXT_CONTEXT_WRONG_XVER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions) @@ -506,21 +506,22 @@ ALL_OK = All OK SEARCHPARAMETER_NOTFOUND = Unable to find the base Search Parameter {0} so can't check that this SearchParameter is a proper derivation from it SEARCHPARAMETER_BASE_WRONG = The resource type {1} is not listed as a base in the SearchParameter this is derived from ({0}) SEARCHPARAMETER_TYPE_WRONG = The type {1} is different to the type {0} in the derivedFrom SearchParameter -SEARCHPARAMETER_EXP_WRONG = The expression "{2}" is not compatible with the expression "{1}" in the derivedFrom SearchParameter {0}, and this likely indicates that the derivation relationship is not valid +SEARCHPARAMETER_EXP_WRONG = The expression '{2}' is not compatible with the expression '{1}' in the derivedFrom SearchParameter {0}, and this likely indicates that the derivation relationship is not valid VALUESET_NO_SYSTEM_WARNING = No System specified, so Concepts and Filters can't be checked VALUESET_INCLUDE_INVALID_CONCEPT_CODE = The code {1} is not valid in the system {0} VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = The code {2} is not valid in the system {0} version {1} VALUESET_UNC_SYSTEM_WARNING = Unknown System specified, so Concepts and Filters can't be checked VALUESET_UNC_SYSTEM_WARNING_VER = Unknown System/Version specified, so Concepts and Filters can't be checked -Extension_PROF_Type = The Profile "{0}" definition allows for the type {1} but found type {2} -TYPE_CHECKS_PATTERN_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4} -TYPE_CHECKS_PATTERN_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4} -TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4} -TYPE_CHECKS_FIXED_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4} +Extension_PROF_Type = The Profile '{0}' definition allows for the type {1} but found type {2} +TYPE_CHECKS_PATTERN_CC = The pattern [system {0}, code {1}, and display '{2}'] defined in the profile {3} not found. Issues: {4} +TYPE_CHECKS_PATTERN_CC_US = The pattern [system {0}, code {1}, display '{2}' and userSelected {5}] defined in the profile {3} not found. Issues: {4} +TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display '{2}'] defined in the profile {3} not found. Issues: {4} +TYPE_CHECKS_FIXED_CC_US = The pattern [system {0}, code {1}, display '{2}' and userSelected {5}] defined in the profile {3} not found. Issues: {4} Internal_error = Internal error: {0} -VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN = Global Profile reference "{0}" from IG {1} could not be resolved, so has not been checked +VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN = Global Profile reference '{0}' from IG {1} could not be resolved, so has not been checked VALIDATION_VAL_PROFILE_SIGNPOST = Validate resource against profile {0} VALIDATION_VAL_PROFILE_SIGNPOST_META = Validate resource against profile {0} - listed in meta +VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM = Validate resource against profile {0} - provided as bundle param VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = Validate resource against profile {0} - a global profile in {1} ERROR_GENERATING_SNAPSHOT = Error generating Snapshot: {0} (this usually arises from a problem in the differential) SNAPSHOT_EXISTING_PROBLEM = The generated snapshot has a different number of elements {1} that the originally provided snapshot {0} @@ -547,7 +548,7 @@ FHIRPATH_NO_COLLECTION = Error evaluating FHIRPath expression: The function {0} FHIRPATH_NOT_IMPLEMENTED = Error evaluating FHIRPath expression: The function {0} is not implemented FHIRPATH_PARAM_WRONG = Error evaluating FHIRPath expression: The expression type {0} is not supported for parameter {1} on function {2} FHIRPATH_CHECK_FAILED = Error evaluating FHIRPath expression: The check {0} failed -FHIRPATH_NO_TYPE = Error evaluating FHIRPath expression: The type "{0}" is unknown or not supported at {1} +FHIRPATH_NO_TYPE = Error evaluating FHIRPath expression: The type '{0}' is unknown or not supported at {1} FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED = Error in discriminator at {0}: found a sliced element while resolving the fixed value for one of the slices FHIRPATH_DISCRIMINATOR_THIS_CANNOT_FIND = Problem with use of resolve() - profile {0} on {1} could not be resolved FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE = illegal use of resolve() in discriminator - no type on element {0} @@ -591,3 +592,7 @@ RENDER_BUNDLE_IF_NON_MATCH = If-None-Match = {0} RENDER_BUNDLE_IF_MOD = If-Modified-Since = {0} RENDER_BUNDLE_IF_MATCH = If-Match = {0} RENDER_BUNDLE_IF_NONE = If-None-Exist = {0} +BUNDLE_RULE_NONE = No Rule +BUNDLE_RULE_UNKNOWN = Bundle Rule refers to invalid resource {0} +BUNDLE_RULE_INVALID_INDEX = Bundle Rules index is invalid ({0}) +BUNDLE_RULE_PROFILE_UNKNOWN = Bundle Rules profile {1} is unknown for {0} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index e02dac771..2cfaca357 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -307,6 +307,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private Locale locale; private List igs = new ArrayList<>(); private boolean showTimes; + private List bundleValidationRules = new ArrayList<>(); private class AsteriskFilter implements FilenameFilter { String dir; @@ -1558,6 +1559,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst validator.getContext().setLocale(locale); validator.setFetcher(this); validator.getImplementationGuides().addAll(igs); + validator.getBundleValidationRules().addAll(bundleValidationRules); return validator; } @@ -2368,6 +2370,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public FilesystemPackageCacheManager getPcm() { return pcm; } - + + public List getBundleValidationRules() { + return bundleValidationRules; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index 1b0bda203..1ed9893a9 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -1,6 +1,8 @@ package org.hl7.fhir.validation.cli.model; import com.fasterxml.jackson.annotation.JsonProperty; + +import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; import org.hl7.fhir.validation.Validator; import java.util.*; @@ -77,6 +79,9 @@ public class CliContext { @JsonProperty("locations") private Map locations = new HashMap(); + // TODO: Mark what goes here? + private List bundleValidationRules = new ArrayList<>(); + @JsonProperty("map") public String getMap() { @@ -100,6 +105,11 @@ public class CliContext { return this; } + // TODO: Mark what goes here? + public List getBundleValidationRules() { + return bundleValidationRules; + } + public CliContext addIg(String ig) { if (this.igs == null) { this.igs = new ArrayList<>(); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index 96b5066da..b4fb2ed7c 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -216,6 +216,7 @@ public class ValidationService { validator.setCrumbTrails(cliContext.isCrumbTrails()); validator.setShowTimes(cliContext.isShowTimes()); validator.setFetcher(new StandAloneValidatorFetcher(validator.getPcm(), validator.getContext(), validator)); + validator.getBundleValidationRules().addAll(cliContext.getBundleValidationRules()); TerminologyCache.setNoCaching(cliContext.isNoInternalCaching()); return validator; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index f3f067b76..68638cdfc 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -1,5 +1,6 @@ package org.hl7.fhir.validation.cli.utils; +import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.validation.Validator; import org.hl7.fhir.validation.cli.model.CliContext; @@ -15,6 +16,7 @@ public class Params { public static final String OUTPUT = "-output"; public static final String PROXY = "-proxy"; public static final String PROFILE = "-profile"; + public static final String BUNDLE = "-bundle"; public static final String QUESTIONNAIRE = "-questionnaire"; public static final String NATIVE = "-native"; public static final String ASSUME_VALID_REST_REF = "-assumeValidRestReferences"; @@ -102,15 +104,21 @@ public class Params { } else { p = args[++i]; cliContext.addProfile(p); + } + } else if (args[i].equals(BUNDLE)) { + String p = null; + String r = null; + if (i + 1 == args.length) { + throw new Error("Specified -profile without indicating bundle rule "); + } else { + r = args[++i]; } - if (p != null && i + 1 < args.length && args[i + 1].equals("@")) { - i++; - if (i + 1 == args.length) { - throw new Error("Specified -profile with @ without indicating profile location"); - } else { - cliContext.addLocation(p, args[++i]); - } + if (i + 1 == args.length) { + throw new Error("Specified -profile without indicating profile source"); + } else { + p = args[++i]; } + cliContext.getBundleValidationRules().add(new BundleValidationRule(r, p)); } else if (args[i].equals(QUESTIONNAIRE)) { if (i + 1 == args.length) throw new Error("Specified -questionnaire without indicating questionnaire file"); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 554bded5a..77f941dc0 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -364,6 +364,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean securityChecks; private ProfileUtilities profileUtilities; private boolean crumbTrails; + private List bundleValidationRules = new ArrayList<>(); public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) { super(theContext); @@ -3624,7 +3625,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat public void checkSpecials(ValidatorHostContext hostContext, List errors, Element element, NodeStack stack, boolean checkSpecials) { // specific known special validations if (element.getType().equals(BUNDLE)) { - new BundleValidator(context, serverBase).validateBundle(errors, element, stack, checkSpecials); + new BundleValidator(context, serverBase, this).validateBundle(errors, element, stack, checkSpecials, hostContext); } else if (element.getType().equals("Observation")) { validateObservation(errors, element, stack); } else if (element.getType().equals("Questionnaire")) { @@ -4818,4 +4819,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat this.securityChecks = securityChecks; } + @Override + public List getBundleValidationRules() { + return bundleValidationRules ; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java index a2f7760bd..fe5b931ea 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java @@ -1,6 +1,7 @@ package org.hl7.fhir.validation.instance.type; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -11,26 +12,32 @@ import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; 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.IssueType; import org.hl7.fhir.validation.BaseValidator; +import org.hl7.fhir.validation.instance.InstanceValidator; import org.hl7.fhir.validation.instance.utils.EntrySummary; import org.hl7.fhir.validation.instance.utils.IndexedElement; import org.hl7.fhir.validation.instance.utils.NodeStack; +import org.hl7.fhir.validation.instance.utils.ValidatorHostContext; public class BundleValidator extends BaseValidator{ public final static String URI_REGEX3 = "((http|https)://([A-Za-z0-9\\\\\\.\\:\\%\\$]*\\/)*)?(Account|ActivityDefinition|AllergyIntolerance|AdverseEvent|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BodySite|Bundle|CapabilityStatement|CarePlan|CareTeam|ChargeItem|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition (aka Problem)|Consent|Contract|Coverage|DataElement|DetectedIssue|Device|DeviceComponent|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EligibilityRequest|EligibilityResponse|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|ExpansionProfile|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingManifest|ImagingStudy|Immunization|ImmunizationRecommendation|ImplementationGuide|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationRequest|MedicationStatement|MessageDefinition|MessageHeader|NamingSystem|NutritionOrder|Observation|OperationDefinition|OperationOutcome|Organization|Parameters|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|ProcedureRequest|ProcessRequest|ProcessResponse|Provenance|Questionnaire|QuestionnaireResponse|ReferralRequest|RelatedPerson|RequestGroup|ResearchStudy|ResearchSubject|RiskAssessment|Schedule|SearchParameter|Sequence|ServiceDefinition|Slot|Specimen|StructureDefinition|StructureMap|Subscription|Substance|SupplyDelivery|SupplyRequest|Task|TestScript|TestReport|ValueSet|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?"; private String serverBase; + private InstanceValidator validator; - public BundleValidator(IWorkerContext context, String serverBase) { + public BundleValidator(IWorkerContext context, String serverBase, InstanceValidator validator) { super(context); this.serverBase = serverBase; + this.validator = validator; } - public void validateBundle(List errors, Element bundle, NodeStack stack, boolean checkSpecials) { + public void validateBundle(List errors, Element bundle, NodeStack stack, boolean checkSpecials, ValidatorHostContext hostContext) { List entries = new ArrayList(); bundle.getNamedChildren(ENTRY, entries); String type = bundle.getNamedChildValue(TYPE); @@ -68,7 +75,12 @@ public class BundleValidator extends BaseValidator{ // We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles // validateResourceIds(errors, entries, stack); } + + int count = 0; + Map counter = new HashMap<>(); + for (Element entry : entries) { + NodeStack estack = stack.push(entry, count, null, null); String fullUrl = entry.getNamedChildValue(FULL_URL); String url = getCanonicalURLForEntry(entry); String id = getIdForEntry(entry); @@ -77,7 +89,30 @@ public class BundleValidator extends BaseValidator{ rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MISMATCHIDURL, url, fullUrl, id); rule(errors, 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); } + + // check bundle profile requests + if (entry.hasChild(RESOURCE)) { + String rtype = entry.getNamedChild(RESOURCE).fhirType(); + int rcount = counter.containsKey(rtype) ? counter.get(rtype)+1 : 0; + counter.put(rtype, rcount); + for (BundleValidationRule bvr : validator.getBundleValidationRules()) { + if (meetsRule(bvr, rtype, rcount, count)) { + StructureDefinition defn = validator.getContext().fetchResource(StructureDefinition.class, bvr.getProfile()); + if (defn == null) { + throw new Error(validator.getContext().formatMessage(I18nConstants.BUNDLE_RULE_PROFILE_UNKNOWN, bvr.getRule(), bvr.getProfile())); + } else { + Element res = entry.getNamedChild(RESOURCE); + NodeStack rstack = estack.push(res, -1, null, null); + signpost(errors, IssueType.INFORMATIONAL, res.line(), res.col(), stack.getLiteralPath(), !validator.isCrumbTrails(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM, defn.getUrl()); + stack.resetIds(); + validator.startInner(hostContext, errors, res, res, defn, rstack, false); + } + } + } + } + // todo: check specials + count++; } } @@ -372,4 +407,33 @@ public class BundleValidator extends BaseValidator{ } + public boolean meetsRule(BundleValidationRule bvr, String rtype, int rcount, int count) { + if (bvr.getRule() == null) { + throw new Error(validator.getContext().formatMessage(I18nConstants.BUNDLE_RULE_NONE)); + } + String rule = bvr.getRule(); + String t = rule.contains(":") ? rule.substring(0, rule.indexOf(":")) : Utilities.isInteger(rule) ? null : rule; + String index = rule.contains(":") ? rule.substring(rule.indexOf(":")+1) : Utilities.isInteger(rule) ? rule : null; + if (Utilities.noString(t) && Utilities.noString(index)) { + throw new Error(validator.getContext().formatMessage(I18nConstants.BUNDLE_RULE_NONE)); + } + if (!Utilities.noString(t)) { + if (!validator.getContext().getResourceNames().contains(t)) { + throw new Error(validator.getContext().formatMessage(I18nConstants.BUNDLE_RULE_UNKNOWN, t)); + } + } + if (!Utilities.noString(index)) { + if (!Utilities.isInteger(index)) { + throw new Error(validator.getContext().formatMessage(I18nConstants.BUNDLE_RULE_INVALID_INDEX, index)); + } + } + if (t == null) { + return Integer.toString(count).equals(index); + } else if (index == null) { + return t.equals(rtype); + } else { + return t.equals(rtype) && Integer.toString(rcount).equals(index); + } + } + } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java index f9af15a9c..1a69fd085 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java @@ -36,6 +36,7 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy; import org.hl7.fhir.utilities.TextFile; @@ -185,6 +186,10 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour } } } + val.getBundleValidationRules().clear(); + if (content.has("bundle-param")) { + val.getBundleValidationRules().add(new BundleValidationRule(content.getAsJsonObject("bundle-param").get("rule").getAsString(), content.getAsJsonObject("bundle-param").get("profile").getAsString())); + } if (content.has("profiles")) { for (JsonElement je : content.getAsJsonArray("profiles")) { String filename = je.getAsString();