Merge pull request #1576 from hapifhir/2024-03-gg-codesystem-validation

2024 03 gg codesystem validation
This commit is contained in:
Grahame Grieve 2024-03-18 20:02:48 +11:00 committed by GitHub
commit 85a31df28e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 99 additions and 13 deletions

View File

@ -833,19 +833,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
if ("DomainResource.contained".equals(child.getBase().getPath())) {
if (round2) {
for (BaseWrapper v : p.getValues()) {
if (v.getBase() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) {
if (v.getResource() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) {
x.hr();
RenderingContext ctxt = context.copy();
ctxt.setContained(true);
ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt);
ResourceWrapper rw = null;
if (v.getBase() instanceof org.hl7.fhir.r5.elementmodel.Element) {
rw = new ElementWrappers.ResourceWrapperMetaElement(ctxt, (org.hl7.fhir.r5.elementmodel.Element) v.getBase());
} else if (v.getBase() instanceof Resource){
rw = new DirectWrappers.ResourceWrapperDirect(ctxt, (Resource) v.getBase());
} else {
throw new FHIRException("Not handled: base = "+v.getBase().getClass().getName());
}
ResourceWrapper rw = v.getResource();
rnd.render(x.blockquote(), rw);
}
}

View File

@ -62,6 +62,7 @@ public class BaseWrappers {
public interface BaseWrapper extends WrapperBase {
public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException;
public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException; // for contained, etc
public PropertyWrapper getChildByName(String tail);
public String fhirType();
}

View File

@ -102,6 +102,19 @@ public class DOMWrappers {
return type;
}
@Override
public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException {
Element r = XMLUtil.getFirstChild(element);
StructureDefinition sd = getContext().getContext().fetchTypeDefinition(r.getLocalName());
if (sd == null) {
throw new FHIRException("Unable to find definition for type "+type+" @ "+definition.getPath());
}
if (sd.getKind() != StructureDefinitionKind.RESOURCE) {
throw new FHIRException("Definition for type "+type+" is not for a resource @ "+definition.getPath());
}
return new ResourceWrapperElement(context, r, sd);
}
}
public static class PropertyWrapperElement extends RendererWrapperImpl implements PropertyWrapper {

View File

@ -5,6 +5,7 @@ import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.DomainResource;
@ -164,6 +165,11 @@ public class DirectWrappers {
return wrapped.fhirType();
}
@Override
public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException {
return new DirectWrappers.ResourceWrapperDirect(getContext(), (Resource) wrapped);
}
}
public static class ResourceWrapperDirect extends WrapperBaseImpl implements ResourceWrapper {

View File

@ -109,6 +109,11 @@ public class ElementWrappers {
return element.fhirType();
}
@Override
public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException {
return new ElementWrappers.ResourceWrapperMetaElement(getContext(), element);
}
}
public static class ResourceWrapperMetaElement extends WrapperBaseImpl implements ResourceWrapper {

View File

@ -234,6 +234,15 @@ public class CodeSystemUtilities extends TerminologyUtilities {
concept.addProperty().setCode(code).setValue(value);
}
public static void setProperty(CodeSystem cs, ConceptDefinitionComponent concept, String url, String code, DataType value) throws FHIRFormatError {
defineProperty(cs, code, propertyTypeForValue(value), url);
ConceptPropertyComponent p = getProperty(concept, code);
if (p != null)
p.setValue(value);
else
concept.addProperty().setCode(code).setValue(value);
}
private static PropertyType propertyTypeForValue(DataType value) {
if (value instanceof BooleanType) {
@ -262,6 +271,9 @@ public class CodeSystemUtilities extends TerminologyUtilities {
private static String defineProperty(CodeSystem cs, String code, PropertyType pt) {
String url = "http://hl7.org/fhir/concept-properties#"+code;
return defineProperty(cs, code, pt, url);
}
private static String defineProperty(CodeSystem cs, String code, PropertyType pt, String url) {
for (PropertyComponent p : cs.getProperty()) {
if (p.hasCode() && p.getCode().equals(code)) {
if (!p.getUri().equals(url)) {

View File

@ -2235,4 +2235,11 @@ public class Utilities {
return true;
}
public static String stripEoln(String text) {
if (text == null) {
return "";
}
return text.replace("\r\n", " ").replace("\n", " ").replace("\r", " ");
}
}

View File

@ -1101,6 +1101,7 @@ public class I18nConstants {
public static final String VALUESET_BAD_FILTER_VALUE_HAS_COMMA = "VALUESET_BAD_FILTER_VALUE_HAS_COMMA";
public static final String VALUESET_BAD_FILTER_VALUE_VALID_REGEX = "VALUESET_BAD_FILTER_VALUE_VALID_REGEX";
public static final String VALUESET_BAD_PROPERTY_NO_REGEX = "VALUESET_BAD_PROPERTY_NO_REGEX";
public static final String CODESYSTEM_PROPERTY_CODE_WARNING = "CODESYSTEM_PROPERTY_CODE_WARNING";
}

View File

@ -1159,4 +1159,5 @@ VALUESET_BAD_FILTER_OP = The operation ''{0}'' is not allowed for property ''{1}
VALUESET_BAD_FILTER_VALUE_HAS_COMMA = The filter value has a comma, but the operation is different to 'in' and 'not-in', so the comma will be interpreted as part of the {0} value
VALUESET_BAD_FILTER_VALUE_VALID_REGEX = The value for a filter based on property ''{0}'' should be a valid regex, not ''{1}'' (err = ''{2}'')
VALUESET_BAD_PROPERTY_NO_REGEX = Cannot apply a regex filter to the property ''{0}'' (usually regex filters are applied to the codes, or a named property of the code system)
CODESYSTEM_PROPERTY_CODE_WARNING = If the type is ''code'', then the valueSet property should be provided to clarify what kind of code will be found in the element

View File

@ -3344,7 +3344,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
Set<String> refs = new HashSet<>();
int count = countTargetMatches(resource, ref, true, "$", refs);
if (count == 0) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE, href, xpath, node.allText());
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE, href, xpath, Utilities.stripEoln(node.allText()));
} else if (count > 1) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES, href, xpath, node.allText(), CommaSeparatedStringBuilder.join(", ", refs));
}
@ -5760,7 +5760,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null && !url.contains("http://hl7.org/fhir/sid"), I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) {
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null || url.contains("http://hl7.org/fhir/sid"), I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) {
HL7WorkGroup wgd = HL7WorkGroups.find(wg);
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wgd != null, I18nConstants.VALIDATION_HL7_WG_UNKNOWN, wg)) {
String rpub = "HL7 International / "+wgd.getName();

View File

@ -14,6 +14,7 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -57,16 +58,30 @@ public class CodeSystemValidator extends BaseValidator {
}
public enum CodeValidationRule {
NO_VALIDATION, INTERNAL_CODE, VS_ERROR, VS_WARNING
}
public class PropertyDef {
private String uri;
private String code;
private String type;
private CodeValidationRule rule;
private String valueset;
protected PropertyDef(String uri, String code, String type) {
super();
this.uri = uri;
this.code = code;
this.type = type;
}
public void setCodeValidationRules(CodeValidationRule rule, String valueset) {
this.rule = rule;
this.valueset = valueset;
}
public String getUri() {
return uri;
}
@ -76,9 +91,14 @@ public class CodeSystemValidator extends BaseValidator {
public String getType() {
return type;
}
public String getValueset() {
return valueset;
}
}
private static final String VS_PROP_STATUS = null;
public CodeSystemValidator(BaseValidator parent) {
super(parent);
}
@ -252,8 +272,7 @@ public class CodeSystemValidator extends BaseValidator {
ukp = KnownProperty.ItemWeight;
break;
default:
ok = false;
rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_PROPERTY_BAD_HL7_URI, uri);
ok = rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), isBaseSpec(cs.getNamedChildValue("url")), I18nConstants.CODESYSTEM_PROPERTY_BAD_HL7_URI, uri);
}
}
}
@ -309,7 +328,31 @@ public class CodeSystemValidator extends BaseValidator {
if (type != null) {
ok = rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), type.equals(ukp.getType()), I18nConstants.CODESYSTEM_PROPERTY_URI_TYPE_MISMATCH, uri, ukp.getType(),type) && ok;
}
switch (ukp) {
case Child:
case Parent:
case PartOf:
case Synonym:
pd.setCodeValidationRules(CodeValidationRule.INTERNAL_CODE, null);
break;
case Status:
pd.setCodeValidationRules(CodeValidationRule.VS_WARNING, VS_PROP_STATUS);
break;
default:
break;
}
} else if ("code".equals(pd.getType())) {
if (property.hasExtension("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet")) {
pd.setCodeValidationRules(CodeValidationRule.VS_ERROR, property.getExtensionValue("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet").primitiveValue());
} else if (VersionUtilities.isR6Plus(context.getVersion())) {
hint(errors, "2024-03-18", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), ukp != null && type.equals(ukp.getType()), I18nConstants.CODESYSTEM_PROPERTY_CODE_WARNING);
} else {
}
} else if ("Coding".equals(pd.getType()) && property.hasExtension("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet")) {
pd.setCodeValidationRules(CodeValidationRule.VS_ERROR, property.getExtensionValue("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet").primitiveValue());
}
if (uri == null) {
if (ckp == null) {
hint(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_PROPERTY_UNKNOWN_CODE, code);
@ -323,6 +366,10 @@ public class CodeSystemValidator extends BaseValidator {
return ok;
}
private boolean isBaseSpec(String url) {
return url.startsWith("http://hl7.org/fhir/") && !url.substring(20).contains("/");
}
private boolean checkConcept(List<ValidationMessage> errors, Element cs, NodeStack stack, boolean caseSensitive, String hierarchyMeaning, CodeSystem csB, Element concept, Set<String> codes, Map<String, PropertyDef> properties) {
boolean ok = true;
String code = concept.getNamedChildValue("code");