Validate that ConceptMap references to ValueSets are actual value sets

and Validate that abstract classes have concrete subtypes
This commit is contained in:
Grahame Grieve 2025-01-04 10:31:59 +11:00
parent 46e2d251d3
commit 0d6ce842b0
6 changed files with 106 additions and 32 deletions

View File

@ -10,10 +10,12 @@ public class ResourceSorters {
@Override
public int compare(CanonicalResource arg0, CanonicalResource arg1) {
if (arg0.getUrl() != null) {
if (arg0.getUrl() != null && arg1.getUrl() != null) {
return arg0.getUrl().compareTo(arg1.getUrl());
} else if (arg1.getUrl() != null) {
return -arg1.getUrl().compareTo(arg0.getUrl());
return -1;
} else if (arg0.getUrl() != null) {
return 1;
} else {
return 0;
}

View File

@ -278,6 +278,8 @@ public class ToolingExtensions {
public static final String EXT_ALTERNATE_CANONICAL = "http://hl7.org/fhir/StructureDefinition/alternate-canonical";
public static final String EXT_SUPPRESSED = "http://hl7.org/fhir/StructureDefinition/elementdefinition-suppress";
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";
// specific extension helpers
@ -474,6 +476,7 @@ public class ToolingExtensions {
}
return null;
}
public static String readStringExtension(DomainResource c, String uri) {
Extension ex = getExtension(c, uri);
if (ex == null)
@ -495,6 +498,30 @@ public class ToolingExtensions {
return null;
}
public static String readStringSubExtension(DomainResource c, String uri, String name) {
Extension ex = getExtension(c, uri);
if (ex == null)
return null;
ex = getExtension(ex, name);
if (ex == null)
return null;
if ((ex.getValue() instanceof StringType))
return ((StringType) ex.getValue()).getValue();
if ((ex.getValue() instanceof UriType))
return ((UriType) ex.getValue()).getValue();
if (ex.getValue() instanceof CodeType)
return ((CodeType) ex.getValue()).getValue();
if (ex.getValue() instanceof IntegerType)
return ((IntegerType) ex.getValue()).asStringValue();
if (ex.getValue() instanceof Integer64Type)
return ((Integer64Type) ex.getValue()).asStringValue();
if (ex.getValue() instanceof DecimalType)
return ((DecimalType) ex.getValue()).asStringValue();
if ((ex.getValue() instanceof MarkdownType))
return ((MarkdownType) ex.getValue()).getValue();
return null;
}
@SuppressWarnings("unchecked")
public static PrimitiveType<DataType> readPrimitiveExtension(DomainResource c, String uri) {
Extension ex = getExtension(c, uri);

View File

@ -17,6 +17,7 @@ public class UserDataNames {
public static final String SNAPSHOT_DERIVATION_POINTER = "derived.pointer";
public static final String SNAPSHOT_IS_DERIVED = "derived.fact";
public static final String SNAPSHOT_GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
public static final String SNAPSHOT_DERIVATION_DIFF = "profileutilities.snapshot.diffsource";
public static final String SNAPSHOT_GENERATING = "profileutils.snapshot.generating";
public static final String SNAPSHOT_GENERATED = "profileutils.snapshot.generated";
public static final String SNAPSHOT_GENERATED_MESSAGES = "profileutils.snapshot.generated.messages";

View File

@ -1163,4 +1163,6 @@ public class I18nConstants {
public static final String VS_EXP_IMPORT_FAIL_X = "VS_EXP_IMPORT_FAIL_X";
public static final String VS_EXP_FILTER_UNK = "VS_EXP_FILTER_UNK";
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";
}

View File

@ -11,6 +11,7 @@ import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
@ -144,8 +145,10 @@ public class ConceptMapValidator extends BaseValidator {
}
}
}
VSReference sourceScope = readVSReference(cm, "sourceScope", "source");
VSReference targetScope = readVSReference(cm, "targetScope", "target");
BooleanHolder bh = new BooleanHolder();
VSReference sourceScope = readVSReference(errors, stack, bh, cm, "sourceScope", "source");
VSReference targetScope = readVSReference(errors, stack, bh, cm, "targetScope", "target");
ok = ok && bh.ok();
List<Element> groups = cm.getChildrenByName("group");
int ci = 0;
@ -191,7 +194,7 @@ public class ConceptMapValidator extends BaseValidator {
}
private VSReference readVSReference(Element cm, String... names) {
private VSReference readVSReference(List<ValidationMessage> errors, NodeStack stack,BooleanHolder bok, Element cm, String... names) {
for (String n : names) {
if (cm.hasChild(n, false)) {
Element e = cm.getNamedChild(n, false);
@ -206,10 +209,32 @@ public class ConceptMapValidator extends BaseValidator {
if (ref.contains("|")) {
res.url = ref.substring(0, ref.indexOf("|"));
res.version = ref.substring(ref.indexOf("|")+1);
res.vs = context.findTxResource(ValueSet.class, res.url, res.version);
Resource r = context.fetchResource(Resource.class, res.url, res.version);
if (r != null) {
if (r instanceof ValueSet) {
res.vs = (ValueSet) r;
} else {
bok.fail();
rule(errors, "2025-12-31", IssueType.INVALID, stack.getLiteralPath()+"."+n, false, I18nConstants.CONCEPTMAP_VS_NOT_A_VS, r.fhirType());
}
}
if (res.vs == null) {
res.vs = context.findTxResource(ValueSet.class, res.url, res.version);
}
} else {
res.url = ref;
res.vs = context.findTxResource(ValueSet.class, res.url);
Resource r = context.fetchResource(Resource.class, res.url);
if (r != null) {
if (r instanceof ValueSet) {
res.vs = (ValueSet) r;
} else {
bok.fail();
rule(errors, "2025-12-31", IssueType.INVALID, stack.getLiteralPath()+"."+n, false, I18nConstants.CONCEPTMAP_VS_NOT_A_VS, r.fhirType());
}
}
if (res.vs == null) {
res.vs = context.findTxResource(ValueSet.class, res.url);
}
}
return res;
}

View File

@ -162,6 +162,19 @@ public class StructureDefinitionValidator extends BaseValidator {
warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), !base.getExperimental() || experimental, I18nConstants.SD_BASE_EXPERIMENTAL, sd.getBaseDefinition());
}
String abstractV = src.getNamedChildValue("abstract");
if ("true".equals(abstractV)) {
String burl = src.getNamedChildValue("url");
if (burl != null) {
boolean bok = false;
for (StructureDefinition sdb : context.fetchResourcesByType(StructureDefinition.class)) {
if (burl.equals(sdb.getBaseDefinition())) {
bok = true;
}
}
warning(errors, "2024-12-31", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_DERIVATION_NO_CONCRETE, typeName);
}
}
List<Element> differentials = src.getChildrenByName("differential");
List<Element> snapshots = src.getChildrenByName("snapshot");
boolean logical = "logical".equals(src.getNamedChildValue("kind", false));
@ -684,31 +697,35 @@ public class StructureDefinitionValidator extends BaseValidator {
boolean found = false;
for (Element e : extensions) {
if (ToolingExtensions.EXT_TYPE_PARAMETER.equals(e.getNamedChildValue("url"))) {
String ename = e.getExtensionValue("name").primitiveValue();
if (name.equals(ename)) {
found = true;
String etype = e.getExtensionValue("type").primitiveValue();
for (Extension ex : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)) {
String tn = ex.getExtensionString("name");
if (tn != null && tn.equals(etype)) {
etype = ex.getExtensionString("type");
break;
}
}
StructureDefinition esd = context.fetchTypeDefinition(etype);
if (rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), esd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, etype)) {
StructureDefinition t = esd;
while (t != null && t != psd) {
t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
}
ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_INVALID_REF, tc, etype, tsd.getVersionedUrl(), name, type) & ok;
if (t != null) {
if (!sd.getAbstract() && esd.getAbstract()) {
warning(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_ABSTRACT_WARNING, tc, etype, tsd.getVersionedUrl(), name, type);
if (!e.hasExtension("name")) {
rule(errors, "2024-12-31", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, "no name");
} else {
String ename = e.getExtensionValue("name").primitiveValue();
if (name.equals(ename)) {
found = true;
String etype = e.getExtensionValue("type").primitiveValue();
for (Extension ex : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)) {
String tn = ex.getExtensionString("name");
if (tn != null && tn.equals(etype)) {
etype = ex.getExtensionString("type");
break;
}
}
} else {
ok = false;
StructureDefinition esd = context.fetchTypeDefinition(etype);
if (rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), esd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, etype)) {
StructureDefinition t = esd;
while (t != null && t != psd) {
t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
}
ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_INVALID_REF, tc, etype, tsd.getVersionedUrl(), name, type) & ok;
if (t != null) {
if (!sd.getAbstract() && esd.getAbstract()) {
warning(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_ABSTRACT_WARNING, tc, etype, tsd.getVersionedUrl(), name, type);
}
}
} else {
ok = false;
}
}
}
}