From 0c4da028b3e976ef42451ac832d18134ff6d6d47 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Nov 2024 22:14:49 +1030 Subject: [PATCH] fix slicing by type and profile to allow multiple options per slice --- .../hl7/fhir/r5/fhirpath/FHIRPathEngine.java | 11 ++++- .../fhir/utilities/i18n/I18nConstants.java | 7 +++- .../src/main/resources/Messages.properties | 12 +++--- .../instance/InstanceValidator.java | 42 ++++++++++--------- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java index 93b8a9f19..3db26b7e7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java @@ -604,7 +604,7 @@ public class FHIRPathEngine { warnings.addAll(typeWarnings); return res; } - + public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List warnings) throws FHIRLexerException, PathEngineException, DefinitionException { typeWarnings.clear(); TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false, expr); @@ -612,6 +612,13 @@ public class FHIRPathEngine { return res; } + public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List warnings, boolean canBeNone) throws FHIRLexerException, PathEngineException, DefinitionException { + typeWarnings.clear(); + TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, canBeNone, expr); + warnings.addAll(typeWarnings); + return res; + } + /** * check that paths referred to in the ExpressionNode are valid * @@ -6585,7 +6592,7 @@ public class FHIRPathEngine { /** given an element definition in a profile, what element contains the differentiating fixed - * for the element, given the differentiating expresssion. The expression is only allowed to + * for the element, given the differentiating expression. The expression is only allowed to * use a subset of FHIRPath * * @param profile 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 0d4b2a79d..e5c029607 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 @@ -178,7 +178,6 @@ public class I18nConstants { public static final String DIFFERENTIAL_WALKS_INTO____BUT_THE_BASE_DOES_NOT_AND_THERE_IS_NOT_A_SINGLE_FIXED_TYPE_THE_TYPE_IS__THIS_IS_NOT_HANDLED_YET = "Differential_walks_into____but_the_base_does_not_and_there_is_not_a_single_fixed_type_The_type_is__This_is_not_handled_yet"; public static final String DISCRIMINATOR_BAD_PATH = "DISCRIMINATOR_BAD_PATH"; public static final String DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0 = "Discriminator__is_based_on_element_existence_but_slice__neither_sets_min1_or_max0"; - public static final String DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES = "Discriminator__is_based_on_type_but_slice__in__has_multiple_types"; public static final String DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES = "Discriminator__is_based_on_type_but_slice__in__has_no_types"; public static final String DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF = "Display_Name_for__should_be_one_of__instead_of"; public static final String DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF = "Display_Name_WS_for__should_be_one_of__instead_of"; @@ -475,7 +474,6 @@ public class I18nConstants { public static final String PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__ = "Problem_processing_expression__in_profile__path__"; public static final String PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_ = "Profile_based_discriminators_must_have_a_type_with_a_profile__in_profile_"; public static final String PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_ = "Profile_based_discriminators_must_have_a_type__in_profile_"; - public static final String PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE = "Profile_based_discriminators_must_have_only_one_type__in_profile"; public static final String PROFILE_EXT_NOT_HERE = "Profile_EXT_Not_Here"; public static final String PROFILE_VAL_MISSINGELEMENT = "Profile_VAL_MissingElement"; public static final String PROFILE_VAL_NOTALLOWED = "Profile_VAL_NotAllowed"; @@ -1122,4 +1120,9 @@ public class I18nConstants { public static final String CODESYSTEM_CS_COMPLETE_AND_EMPTY = "CODESYSTEM_CS_COMPLETE_AND_EMPTY"; public static final String VALIDATION_VAL_VERSION_NOHASH = "VALIDATION_VAL_VERSION_NOHASH"; public static final String PRIMITIVE_TOO_SHORT = "PRIMITIVE_TOO_SHORT"; + public static final String CANONICAL_MULTIPLE_VERSIONS_KNOWN = "CANONICAL_MULTIPLE_VERSIONS_KNOWN"; + public static final String SD_PATH_NO_SLICING = "SD_PATH_NO_SLICING"; + public static final String SD_PATH_SLICING_DEPRECATED = "SD_PATH_SLICING_DEPRECATED"; + public static final String SD_PATH_NOT_VALID = "SD_PATH_NOT_VALID"; + public static final String SD_PATH_ERROR = "SD_PATH_ERROR"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 1f44ca339..b4c87aeaa 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -179,8 +179,6 @@ Did_not_find_type_root_ = Did not find type root: {0} Differential_does_not_have_a_slice__b_of_____in_profile_ = Differential in profile {5} does not have a slice at {6} (on {0}, position {1} of {2} / {3} / {4}) Differential_walks_into____but_the_base_does_not_and_there_is_not_a_single_fixed_type_The_type_is__This_is_not_handled_yet = Differential walks into ''{0} (@ {1})'', but the base does not, and there is not a single fixed type. The type is {2}. This is not handled yet Discriminator__is_based_on_element_existence_but_slice__neither_sets_min1_or_max0 = Discriminator ({0}) is based on element existence, but slice {1} neither sets min>=1 or max=0 -Discriminator__is_based_on_type_but_slice__in__has_multiple_types_one = -Discriminator__is_based_on_type_but_slice__in__has_multiple_types_other = Discriminator ({1}) is based on type, but slice {2} in {3} has {0} types: {4} Discriminator__is_based_on_type_but_slice__in__has_no_types = Discriminator ({0}) is based on type, but slice {1} in {2} has no types Display_Name_WS_for__should_be_one_of__instead_of_one = Wrong whitespace in Display Name ''{4}'' for {1}#{2}. Valid display is {3} (for the language(s) ''{5}'') Display_Name_WS_for__should_be_one_of__instead_of_other = Wrong whitespace in Display Name ''{4}'' for {1}#{2}. Valid display is one of {0} choices: {3} (for the language(s) ''{5}'') @@ -486,8 +484,6 @@ Profile___has_no_base_and_no_snapshot = Profile {0} ({1}) has no base and no sna 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} Profile_based_discriminators_must_have_a_type__in_profile_ = Profile based discriminators must have a type ({0} in profile {1}) Profile_based_discriminators_must_have_a_type_with_a_profile__in_profile_ = Profile based discriminators must have a type with a profile ({0} in profile {1}) -Profile_based_discriminators_must_have_only_one_type__in_profile_one = -Profile_based_discriminators_must_have_only_one_type__in_profile_other = Profile based discriminators must have only one type ({1} in profile {2}) but found {0} types QUESTIONNAIRE_QR_ITEM_BADOPTION_CS = The code provided {1} cannot be validated in the options value set ({2}) in the questionnaire because the system {0} could not be found QUESTIONNAIRE_Q_DERIVATION_TYPE_IGNORED = The derivation type ''{0}'' means that no derivation checking has been performed against this questionnaire QUESTIONNAIRE_Q_DERIVATION_TYPE_UNKNOWN = The derivation type ''{0}'' is unknown, which means that no derivation checking has been performed against this questionnaire @@ -1090,7 +1086,7 @@ 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 XSI_TYPE_UNNECESSARY = xsi:type is unnecessary at this point XSI_TYPE_WRONG = The xsi:type value ''{0}'' is wrong (should be ''{1}''). Note that xsi:type is unnecessary at this point -_DT_Fixed_Wrong = Value is ''{0}'' but must be ''{1}''{2} +_DT_Fixed_Wrong = Value is ''{0}'' but is fixed to ''{1}'' in the profile {2} _has_children__and_multiple_types__in_profile_ = {0} has children ({1}) and multiple types ({2}) in profile {3} _has_children__for_type__in_profile__but_cant_find_type = {0} has children ({1}) for type {2} in profile {3}, but can''t find type _has_no_children__and_no_types_in_profile_ = {0} has no children ({1}) and no types in profile {2} @@ -1155,4 +1151,8 @@ SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE = The Usage Context value must be of CODESYSTEM_CS_COMPLETE_AND_EMPTY = When a CodeSystem has content = ''complete'', it doesn't make sense for there to be no concepts defined VALIDATION_VAL_VERSION_NOHASH = Version ''{0}'' contains a ''#'', which as this character is used in some URLs to separate the version and the fragment id. When version does include '#', systems will not be able to parse the URL PRIMITIVE_TOO_SHORT = Value ''{0}'' is shorter than permitted minimum length of {1} - \ No newline at end of file +CANONICAL_MULTIPLE_VERSIONS_KNOWN = The version {2} for the {0} {1} is not known. These versions are known: {3} +SD_PATH_SLICING_DEPRECATED = The discriminator type ''{0}'' has been deprecated. Use type=fixed with a pattern[x] instead +SD_PATH_NOT_VALID = The discriminator path ''{0}'' does not appear to be valid for the element that is being sliced ''{1}'' +SD_PATH_ERROR = The discriminator path ''{0}'' does not appear to be valid for the element that is being sliced ''{1}'': {2} + 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 0b8f31c0c..c598bae08 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 @@ -1047,7 +1047,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private boolean check(String v1, String v2) { - return v1 == null ? Utilities.noString(v1) : v1.equals(v2); + boolean res = v1 == null ? Utilities.noString(v1) : v1.equals(v2); + return res; } private boolean checkAddress(List errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern, String context) { @@ -3209,10 +3210,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } if (context.hasFixed()) { - ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, "") && ok; + ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, profile.getVersionedUrl()+"#"+context.getId()) && ok; } if (context.hasPattern()) { - ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, "") && ok; + ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, profile.getVersionedUrl()+"#"+context.getId()) && ok; } if (ok && !ID_EXEMPT_LIST.contains(e.fhirType())) { // ids get checked elsewhere @@ -5129,12 +5130,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if ("0".equals(criteriaElement.getMax())) { expression.append(" and " + discriminator + ".empty()"); } else if (s.getType() == DiscriminatorType.TYPE) { - String type = null; if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) { discriminator = discriminator.substring(0, discriminator.indexOf('[')); String lastNode = tail(discriminator); - type = makeTypeForFHIRPath(criteriaElement.getPath()).substring(lastNode.length()); + String type = makeTypeForFHIRPath(criteriaElement.getPath()).substring(lastNode.length()); + expression.append(" and " + discriminator + " is " + type); } else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) { + String type = null; if (discriminator.contains("[")) discriminator = discriminator.substring(0, discriminator.indexOf('[')); if (criteriaElement.hasType()) { @@ -5144,23 +5146,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else { throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl())); } + expression.append(" and " + discriminator + " is " + type); } else if (criteriaElement.getType().size() > 1) { - throw new DefinitionException(context.formatMessagePlural(criteriaElement.getType().size(), I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES, discriminator, ed.getId(), profile.getVersionedUrl(), criteriaElement.typeSummary())); + CommaSeparatedStringBuilder cb = new CommaSeparatedStringBuilder(" or "); + for (TypeRefComponent tr : criteriaElement.getType()) { + String type = makeTypeForFHIRPath(tr.getWorkingCode()); + cb.append(discriminator + " is " + type); + } + expression.append(" and (" + cb.toString()+")"); } else throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl())); - if (discriminator.isEmpty()) { - expression.append(" and $this is " + type); - } else { - expression.append(" and " + discriminator + " is " + type); - } } else if (s.getType() == DiscriminatorType.PROFILE) { if (criteriaElement.getType().size() == 0) { throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl())); } - if (criteriaElement.getType().size() != 1) { - throw new DefinitionException(context.formatMessagePlural(criteriaElement.getType().size(), I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE, criteriaElement.getId(), profile.getVersionedUrl())); + List list = new ArrayList<>(); + boolean ref = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()"); + for (TypeRefComponent tr : criteriaElement.getType()) { + list.addAll(ref ? tr.getTargetProfile() : tr.getProfile()); } - List list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile(); if (list.size() == 0) { // we don't have to find something // throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl())); @@ -6357,10 +6361,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ValidationInfo vi = element.addDefinition(profile, definition, mode); if (definition.getFixed() != null) { - ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getVersionedUrl(), definition.getSliceName(), null, false, "") && ok; + ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getVersionedUrl(), definition.getSliceName(), null, false, profile.getVersionedUrl()+"#"+definition.getId()) && ok; } if (definition.getPattern() != null) { - ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getVersionedUrl(), definition.getSliceName(), null, true, "") && ok; + ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getVersionedUrl(), definition.getSliceName(), null, true, profile.getVersionedUrl()+"#"+definition.getId()) && ok; } // get the list of direct defined children, including slices @@ -6644,10 +6648,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkPrimitive(valContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, localStack, stack, valContext.getRootResource()) && ok; } else { if (checkDefn.hasFixed()) { - ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, false, "") && ok; + ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, false, profile.getVersionedUrl()+"#"+definition.getId()) && ok; } if (checkDefn.hasPattern()) { - ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, true, "") && ok; + ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, true, profile.getVersionedUrl()+"#"+definition.getId()) && ok; } } if (type.equals("Identifier")) { @@ -7600,7 +7604,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException { if (criteria.hasFixed()) { List msgs = new ArrayList(); - checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getVersionedUrl(), "value", null, false, ""); + checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getVersionedUrl(), "value", null, false, profile.getVersionedUrl()+"#"+criteria.getId()); return msgs.size() == 0; } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) { throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE));