diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 377e1a4bd7..4411048752 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -281,6 +281,8 @@ public class ToolingExtensions { public static final String EXT_SUPPRESS_RESOURCE_TYPE = "http://hl7.org/fhir/tools/StructureDefinition/json-suppress-resourcetype"; public static final String EXT_PROFILE_VIEW_HINT = "http://hl7.org/fhir/tools/StructureDefinition/view-hint"; public static final String EXT_SNAPSHOT_BEHAVIOR = "http://hl7.org/fhir/tools/StructureDefinition/snapshot-behavior"; + public static final String EXT_EARLIEST_FHIR_VERSION = "http://hl7.org/fhir/StructureDefinition/earliestAllowedFHIRVersion"; + public static final String EXT_LATEST_FHIR_VERSION = "http://hl7.org/fhir/StructureDefinition/latestAllowedFHIRVersion"; // specific extension helpers diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java index c30e0674bf..aa013aa0a6 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java @@ -87,6 +87,8 @@ private int compareString(String s1, String s2) { private int compareInteger(String s1, String s2) { if (s1 == null) { return s2 == null ? 0 : 1; + } else if (s2 == null) { + return -1; } else { return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2)); } @@ -329,7 +331,7 @@ public static boolean isSemVer(String version) { if (Utilities.noString(version)) { return false; } - return version.matches("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-\\+]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-\\+][0-9a-zA-Z-\\+]*))*))?$"); + return version.matches("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-\\+]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-\\+][0-9a-zA-Z-\\+]*))*))?)?$"); } /** 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 894a4638c0..7b00f01d4b 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 @@ -1165,4 +1165,6 @@ public class I18nConstants { public static final String NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR = "NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR"; public static final String CONCEPTMAP_VS_NOT_A_VS = "CONCEPTMAP_VS_NOT_A_VS"; public static final String SD_DERIVATION_NO_CONCRETE = "SD_DERIVATION_NO_CONCRETE"; + public static final String EXTENSION_FHIR_VERSION_EARLIEST = "EXTENSION_FHIR_VERSION_EARLIEST"; + public static final String EXTENSION_FHIR_VERSION_LATEST = "EXTENSION_FHIR_VERSION_LATEST"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index e350e1a4bb..36402fd834 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1197,5 +1197,6 @@ VS_EXP_IMPORT_ERROR_X = Unable to expand excluded value set ''{0}'', but no erro VS_EXP_IMPORT_ERROR_TOO_COSTLY = Unable to expand excluded value set ''{0}'': too costly VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported yet CONCEPTMAP_VS_NOT_A_VS = Reference must be to a ValueSet, but found a {0} instead -SD_DERIVATION_NO_CONCRETE = {0} is labeled as an abstract type, but no concrete descendents were found (check definitions - this is usually an error unless concrete definitions are in some other package) - \ No newline at end of file +SD_DERIVATION_NO_CONCRETE = {0} is labeled as an abstract type, but no concrete descendants were found (check definitions - this is usually an error unless concrete definitions are in some other package) +EXTENSION_FHIR_VERSION_EARLIEST = The definition of the extension ''{0}'' specifies that the earliest version it can be used with is {1} (v{2}), but this context of use is version {3} (v{4}) +EXTENSION_FHIR_VERSION_LATEST = The definition of the extension ''{0}'' specifies that the latest version it can be used with is {1} (v{2}), but this context of use is version {3} (v{4}) \ No newline at end of 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 f84957a41a..92df9b87ae 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 @@ -2536,6 +2536,20 @@ private boolean checkExtensionContext(Object appContext, List } Collections.sort(plist); // logical paths are a set, but we want a predictable order for error messages + if (definition.hasExtension(ToolingExtensions.EXT_EARLIEST_FHIR_VERSION) || definition.hasExtension(ToolingExtensions.EXT_LATEST_FHIR_VERSION)) { + if (definition.hasExtension(ToolingExtensions.EXT_EARLIEST_FHIR_VERSION)) { + String v = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_EARLIEST_FHIR_VERSION); + ok = rule(errors, "2025-01-07", IssueType.BUSINESSRULE, container.line(), container.col(), stack.getLiteralPath(), + VersionUtilities.compareVersions(VersionUtilities.getMajMin(context.getVersion()), v) >= 0, + I18nConstants.EXTENSION_FHIR_VERSION_EARLIEST, extUrl, VersionUtilities.getNameForVersion(v), v, VersionUtilities.getNameForVersion(context.getVersion()), context.getVersion()) && ok; + } + if (definition.hasExtension(ToolingExtensions.EXT_LATEST_FHIR_VERSION)) { + String v = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_LATEST_FHIR_VERSION); + ok = rule(errors, "2025-01-07", IssueType.BUSINESSRULE, container.line(), container.col(), stack.getLiteralPath(), + VersionUtilities.compareVersions(VersionUtilities.getMajMin(context.getVersion()), v) <= 0, + I18nConstants.EXTENSION_FHIR_VERSION_LATEST, extUrl, VersionUtilities.getNameForVersion(v), v, VersionUtilities.getNameForVersion(context.getVersion()), context.getVersion()) && ok; + } + } for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) { if (ok) { break; diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index 1ba16e3e08..2ff1884ab7 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -298,6 +298,18 @@ public void test() throws Exception { } } } + if (content.has("supporting5")) { + for (JsonElement e : content.getAsJsonArray("supporting5")) { + String filename = e.getAsString(); + String contents = TestingUtilities.loadTestResource("validator", filename); + CanonicalResource mr = (CanonicalResource) loadResource(filename, contents, "5.0.0"); + logOutput("load resource "+mr.getUrl()); + val.getContext().cacheResource(mr); + if (mr instanceof ImplementationGuide) { + val.getImplementationGuides().add((ImplementationGuide) mr); + } + } + } val.getBundleValidationRules().clear(); if (content.has("bundle-param")) { val.getBundleValidationRules().add(new BundleValidationRule().setRule(content.getAsJsonObject("bundle-param").get("rule").getAsString()).setProfile( content.getAsJsonObject("bundle-param").get("profile").getAsString())); @@ -520,6 +532,10 @@ public StructureDefinition loadProfile(String filename, String contents, List