Validate earliestAllowed and latestAllowed FHIRVersion for extensions

This commit is contained in:
Grahame Grieve 2025-01-07 14:10:27 +11:00
parent 4ccaae1571
commit 63e2ca36da
6 changed files with 40 additions and 3 deletions

View File

@ -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_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_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_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 // specific extension helpers

View File

@ -87,6 +87,8 @@ public class VersionUtilities {
private int compareInteger(String s1, String s2) { private int compareInteger(String s1, String s2) {
if (s1 == null) { if (s1 == null) {
return s2 == null ? 0 : 1; return s2 == null ? 0 : 1;
} else if (s2 == null) {
return -1;
} else { } else {
return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2)); return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
} }
@ -329,7 +331,7 @@ public class VersionUtilities {
if (Utilities.noString(version)) { if (Utilities.noString(version)) {
return false; 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-\\+]*))*))?)?$");
} }
/** /**

View File

@ -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 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 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 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";
} }

View File

@ -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_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 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 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) 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})

View File

@ -2536,6 +2536,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
Collections.sort(plist); // logical paths are a set, but we want a predictable order for error messages 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())) { for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) {
if (ok) { if (ok) {
break; break;

View File

@ -298,6 +298,18 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
} }
} }
} }
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(); val.getBundleValidationRules().clear();
if (content.has("bundle-param")) { 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())); 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 class ValidationTests implements IEvaluationContext, IValidatorResourceFe
} }
public Resource loadResource(String filename, String contents) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException { public Resource loadResource(String filename, String contents) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
return loadResource(filename, contents, version);
}
public Resource loadResource(String filename, String contents, String version) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
try (InputStream inputStream = IOUtils.toInputStream(contents, Charsets.UTF_8)) { try (InputStream inputStream = IOUtils.toInputStream(contents, Charsets.UTF_8)) {
if (filename.contains(".json")) { if (filename.contains(".json")) {
if (Constants.VERSION.equals(version) || "5.0".equals(version)) if (Constants.VERSION.equals(version) || "5.0".equals(version))