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 9fff78ee7..9b06a63e2 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 @@ -347,6 +347,7 @@ public class I18nConstants { public static final String REFERENCE_TO__CANNOT_BE_RESOLVED = "reference_to__cannot_be_resolved"; public static final String REFERENCE__REFERS_TO_A__NOT_A_VALUESET = "Reference__refers_to_a__not_a_ValueSet"; public static final String RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED = "Resource_resolution_services_not_provided"; + public static final String RESOURCE_RES_ID_MALFORMED = "Resource_RES_ID_Malformed"; public static final String RESOURCE_RES_ID_MISSING = "Resource_RES_ID_Missing"; public static final String RESOURCE_RES_ID_PROHIBITED = "Resource_RES_ID_Prohibited"; public static final String RESOURCE_TYPE_MISMATCH_FOR___ = "Resource_type_mismatch_for___"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 8a9d622a3..f5c72b8c7 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -118,6 +118,7 @@ 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}) +Resource_RES_ID_Malformed = Resource id not formatted correctly 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}'' diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties b/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties index 23b9be242..301e92ded 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties @@ -118,6 +118,7 @@ Reference_REF_NoType = Kan type van de doel-resource niet vaststellen Reference_REF_NotFound_Bundle = Gebundelde of contained reference niet gevonden binnen de bundle/resource {0} Reference_REF_ResourceType = Overeenkomende reference voor reference {0} heeft resourceType {1} Reference_REF_WrongTarget = Het type ''{0}'' is geen geldig doel voor dit element (moet een zijn van {1}) +Resource_RES_ID_Malformed = Resource id nicht richtig formatiert Resource_RES_ID_Missing = Resource vereist een id, maar deze ontbreekt Resource_RES_ID_Prohibited = Resource heeft een id, maar dat is niet toegestaan Terminology_PassThrough_TX_Message = {0} voor ''{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 c540b32dc..e5581180c 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 @@ -47,6 +47,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.codec.binary.Base64InputStream; import org.apache.commons.lang3.NotImplementedException; @@ -168,6 +170,8 @@ import org.w3c.dom.Document; import com.google.gson.Gson; import com.google.gson.JsonObject; +import javax.annotation.Nonnull; + /** * Thinking of using this in a java program? Don't! @@ -5165,14 +5169,37 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), type.equals(resourceName), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, type, resourceName); if (ok) { - if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) + if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) { rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING); - else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) + } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) { rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED); + } else if ((idstatus == IdStatus.OPTIONAL || idstatus == IdStatus.REQUIRED) + && (element.getNamedChild(ID) != null) + && (!idFormattedCorrectly(element.getNamedChild(ID).getValue()))) { + rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MALFORMED); + } start(hostContext, errors, element, element, defn, stack); // root is both definition and type } } + /** + * FHIR IDs are constrained to any combination of upper- or lower-case ASCII letters ('A'..'Z', and 'a'..'z', + * numerals ('0'..'9'), '-' and '.', with a length limit of 64 characters. (This might be an integer, an un-prefixed + * OID, UUID or any other identifier pattern that meets these constraints.) + * + * As a heads up, a null String will pass this check. + * + * @param id The id to verify conformance to specification requirements. + * @return {@link Boolean#TRUE} if the passed in ID is a valid resource ID, {@link Boolean#FALSE} otherwise. + */ + protected static boolean idFormattedCorrectly(@Nonnull String id) { + String idRegex = "[A-Za-z0-9\\-\\.]{1,64}"; + Pattern pattern = Pattern.compile(idRegex); + Matcher matcher = pattern.matcher(id); + return matcher.matches(); + } + + private NodeStack getFirstEntry(NodeStack bundle) { List list = new ArrayList(); bundle.getElement().getNamedChildren(ENTRY, list); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/instance/InstanceValidatorTest.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/instance/InstanceValidatorTest.java new file mode 100644 index 000000000..ad2aa6688 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/instance/InstanceValidatorTest.java @@ -0,0 +1,33 @@ +package org.hl7.fhir.validation.instance; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class InstanceValidatorTest { + + + private static Stream provideIdsWithOutcomes() { + return Stream.of( + Arguments.of("1234", true), + Arguments.of("12-34", true), + Arguments.of("12_34", false), + Arguments.of("12.34", true), + Arguments.of("12/34", false), + Arguments.of("1234#", false), + Arguments.of("31415926535897932384626433832795028841971693993751058209749445923", false) // 65 digits + ); + } + + @ParameterizedTest + @MethodSource("provideIdsWithOutcomes") + void idFormattedCorrectly(String id, boolean expected) { + Assertions.assertEquals(InstanceValidator.idFormattedCorrectly(id), expected); + } +} \ No newline at end of file