add check for multiple version matches for a versionless canonical reference

This commit is contained in:
Grahame Grieve 2024-03-08 22:29:51 +11:00
parent 209d38413e
commit 13dcebb3e4
13 changed files with 243 additions and 43 deletions

View File

@ -3298,5 +3298,96 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
return result;
}
@Override
public <T extends Resource> List<T> fetchResourcesByUrl(Class<T> class_, String uri) {
List<T> res = new ArrayList<>();
if (uri != null && !uri.startsWith("#")) {
if (class_ == StructureDefinition.class) {
uri = ProfileUtilities.sdNs(uri, null);
}
assert !uri.contains("|");
if (uri.contains("#")) {
uri = uri.substring(0, uri.indexOf("#"));
}
synchronized (lock) {
if (class_ == Resource.class || class_ == null) {
for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
for (ResourceProxy r : rt.values()) {
if (uri.equals(r.getUrl())) {
res.add((T) r.getResource());
}
}
}
}
if (class_ == ImplementationGuide.class || class_ == Resource.class || class_ == null) {
for (ImplementationGuide cr : guides.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == CapabilityStatement.class || class_ == Resource.class || class_ == null) {
for (CapabilityStatement cr : capstmts.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == Measure.class || class_ == Resource.class || class_ == null) {
for (Measure cr : measures.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == Library.class || class_ == Resource.class || class_ == null) {
for (Library cr : libraries.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == StructureDefinition.class || class_ == Resource.class || class_ == null) {
for (StructureDefinition cr : structures.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == StructureMap.class || class_ == Resource.class || class_ == null) {
for (StructureMap cr : transforms.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == NamingSystem.class || class_ == Resource.class || class_ == null) {
for (NamingSystem cr : systems.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == ValueSet.class || class_ == Resource.class || class_ == null) {
for (ValueSet cr : valueSets.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == CodeSystem.class || class_ == Resource.class || class_ == null) {
for (CodeSystem cr : codeSystems.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == ConceptMap.class || class_ == Resource.class || class_ == null) {
for (ConceptMap cr : maps.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == ActorDefinition.class || class_ == Resource.class || class_ == null) {
for (ActorDefinition cr : actors.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == Requirements.class || class_ == Resource.class || class_ == null) {
for (Requirements cr : requirements.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == PlanDefinition.class || class_ == Resource.class || class_ == null) {
for (PlanDefinition cr : plans.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == OperationDefinition.class || class_ == Resource.class || class_ == null) {
for (OperationDefinition cr : operations.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == Questionnaire.class || class_ == Resource.class || class_ == null) {
for (Questionnaire cr : questionnaires.getForUrl(uri)) {
res.add((T) cr);
}
} else if (class_ == SearchParameter.class || class_ == Resource.class || class_ == null) {
for (SearchParameter cr : searchParameters.getForUrl(uri)) {
res.add((T) cr);
}
}
}
}
return res;
}
}

View File

@ -537,6 +537,16 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
}
}
public List<T> getForUrl(String url) {
List<T> res = new ArrayList<>();
List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = listForUrl.get(url);
if (list != null) {
for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
res.add(t.getResource());
}
}
return res;
}
/**
* This is asking for a packaged version aware resolution

View File

@ -191,6 +191,15 @@ public interface IWorkerContext {
public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_, FhirPublication fhirVersion);
public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_);
/**
* Fetch all the resources for the given URL - all matching versions
*
* @param url
* @return
*/
public <T extends Resource> List<T> fetchResourcesByUrl(Class<T> class_, String url);
/**
* Variation of fetchResource when you have a string type, and don't need the right class
*

View File

@ -6,7 +6,9 @@ import org.hl7.fhir.r5.model.CanonicalResource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public interface IValidatorResourceFetcher {
@ -27,7 +29,7 @@ public interface IValidatorResourceFetcher {
* @return an R5 version of the resource
* @throws URISyntaxException
*/
CanonicalResource fetchCanonicalResource(IResourceValidator validator, String url) throws URISyntaxException;
CanonicalResource fetchCanonicalResource(IResourceValidator validator, Object appContext, String url) throws URISyntaxException;
/**
* Whether to try calling fetchCanonicalResource for this reference (not whether it will succeed - just throw an exception from fetchCanonicalResource if it doesn't resolve. This is a policy thing.
@ -38,4 +40,6 @@ public interface IValidatorResourceFetcher {
* @return
*/
boolean fetchesCanonicalResource(IResourceValidator validator, String url);
Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url);
}

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.r5.context;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.PackageInformation;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Resource;
@ -16,6 +17,8 @@ import net.sourceforge.plantuml.tim.stdlib.GetVariableValue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -85,6 +88,11 @@ public class BaseWorkerContextTests {
return null;
}
@Override
public <T extends Resource> List<T> fetchResourcesByUrl(Class<T> class_, String url) {
return new ArrayList<>();
}
};
baseWorkerContext.expParameters = new Parameters();
return baseWorkerContext;

View File

@ -67,6 +67,8 @@ import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
@ -177,7 +179,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
protected String sessionId = Utilities.makeUuidLC();
protected List<UsageContext> usageContexts = new ArrayList<UsageContext>();
protected ValidationOptions baseOptions = new ValidationOptions(FhirPublication.R5);
protected IValidatorResourceFetcher fetcher;
public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager, boolean debug) {
super();
@ -210,6 +212,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
this.urlRegex = parent.urlRegex;
this.usageContexts.addAll(parent.usageContexts);
this.baseOptions = parent.baseOptions;
this.fetcher = parent.fetcher;
}
private boolean doingLevel(IssueSeverity error) {
@ -1262,13 +1265,15 @@ public class BaseValidator implements IValidationContextResourceLoader {
String[] refBaseParts = ref.substring(0, ref.indexOf("/_history/")).split("/");
resourceType = refBaseParts[0];
id = refBaseParts[1];
targetUrl = base + resourceType+"/"+ id;
} else if (base.startsWith("urn")) {
resourceType = ref.split("/")[0];
id = ref.split("/")[1];
} else
targetUrl = base + id;
} else {
id = ref;
targetUrl = base + id;
targetUrl = base + id;
}
}
List<Element> entries = new ArrayList<Element>();
@ -1621,4 +1626,6 @@ public class BaseValidator implements IValidationContextResourceLoader {
private boolean isContext(Coding use, Coding value, UsageContext usage) {
return usage.getValue() instanceof Coding && context.subsumes(baseOptions, usage.getCode(), use) && context.subsumes(baseOptions, (Coding) usage.getValue(), value);
}
}

View File

@ -14,9 +14,11 @@ import java.util.Arrays;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.fhir.ucum.UcumEssenceService;
@ -1194,7 +1196,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
@Override
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, String url) throws URISyntaxException {
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, Object appContext, String url) throws URISyntaxException {
Resource res = context.fetchResource(Resource.class, url);
if (res != null) {
if (res instanceof CanonicalResource) {
@ -1203,7 +1205,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
return null;
}
}
return fetcher != null ? fetcher.fetchCanonicalResource(validator, url) : null;
return fetcher != null ? fetcher.fetchCanonicalResource(validator, appContext, url) : null;
}
@Override
@ -1234,4 +1236,18 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
return EnumSet.allOf(ElementValidationAction.class);
}
@Override
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
Set<String> res = new HashSet<>();
for (Resource r : context.fetchResourcesByUrl(Resource.class, url)) {
if (r instanceof CanonicalResource) {
res.add(((CanonicalResource) r).getVersion());
}
}
if (fetcher != null) {
res.addAll(fetcher.fetchCanonicalResourceVersions(validator, appContext, url));
}
return res;
}
}

View File

@ -6,9 +6,11 @@ import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
import org.hl7.fhir.exceptions.FHIRException;
@ -272,7 +274,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
}
@Override
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, String url) throws URISyntaxException {
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, Object appContext, String url) throws URISyntaxException {
if (url.contains("|")) {
url = url.substring(0, url.indexOf("|"));
}
@ -327,4 +329,9 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
return EnumSet.allOf(CodedContentValidationAction.class);
}
@Override
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();
}
}

View File

@ -584,7 +584,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean noBindingMsgSuppressed;
private Map<String, Element> fetchCache = new HashMap<>();
private HashMap<Element, ResourceValidationTracker> resourceTracker = new HashMap<>();
private IValidatorResourceFetcher fetcher;
private IValidationPolicyAdvisor policyAdvisor;
long time = 0;
long start = 0;
@ -673,14 +672,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return this;
}
public IValidatorResourceFetcher getFetcher() {
return this.fetcher;
}
public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
this.fetcher = value;
return this;
}
@Override
public IValidationPolicyAdvisor getPolicyAdvisor() {
@ -3118,7 +3109,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
r = loadContainedResource(errors, path, valContext.getRootResource(), url.substring(1), Resource.class);
}
if (r == null) {
r = fetcher.fetchCanonicalResource(this, url);
r = fetcher.fetchCanonicalResource(this, valContext.getAppContext(), url);
}
if (r == null) {
r = this.context.fetchResource(Resource.class, url);
@ -3129,6 +3120,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (rp == ReferenceValidationPolicy.CHECK_VALID) {
// todo....
}
// we resolved one, but if there's no version, check if the reference is potentially ambiguous
if (!url.contains("|") && r instanceof CanonicalResource) {
if (!Utilities.existsInList(context.getBase().getPath(), "ImplementationGuide.dependsOn.uri", "ConceptMap.group.source", "ConceptMap.group.target")) {
// ImplementationGuide.dependsOn.version is mandatory, and ConceptMap is checked in the ConceptMap validator
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(this, valContext.getAppContext(), url);
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
url, ((CanonicalResource) r).getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
}
}
} else {
ok = false;
}
@ -3341,11 +3341,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!Utilities.noString(href) && href.startsWith("#") && !href.equals("#")) {
String ref = href.substring(1);
valContext.getInternalRefs().add(ref);
int count = countTargetMatches(resource, ref, true);
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());
} 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());
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));
}
} else {
// we can't validate at this point. Come back and revisit this some time in the future
@ -3360,24 +3361,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
protected int countTargetMatches(Element element, String fragment, boolean checkBundle) {
protected int countTargetMatches(Element element, String fragment, boolean checkBundle, String path,Set<String> refs) {
int count = 0;
if (fragment.equals(element.getIdBase())) {
count++;
refs.add(path+"/id");
}
if (element.getXhtml() != null) {
count = count + countTargetMatches(element.getXhtml(), fragment);
count = count + countTargetMatches(element.getXhtml(), fragment, path, refs);
}
if (element.hasChildren()) {
for (Element child : element.getChildren()) {
count = count + countTargetMatches(child, fragment, false);
count = count + countTargetMatches(child, fragment, false, path+"/"+child.getName(), refs);
}
}
if (count == 0 && checkBundle) {
Element e = element.getParentForValidator();
while (e != null) {
if (e.fhirType().equals("Bundle")) {
return countTargetMatches(e, fragment, false);
return countTargetMatches(e, fragment, false, path+"/..", refs);
}
e = e.getParentForValidator();
}
@ -3385,17 +3387,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return count;
}
private int countTargetMatches(XhtmlNode node, String fragment) {
private int countTargetMatches(XhtmlNode node, String fragment, String path,Set<String> refs) {
int count = 0;
if (fragment.equals(node.getAttribute("id"))) {
count++;
refs.add(path+"/@id");
}
if ("a".equals(node.getName()) && fragment.equals(node.getAttribute("name"))) {
count++;
refs.add(path+"/@name");
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
count = count + countTargetMatches(child, fragment);
count = count + countTargetMatches(child, fragment, path+"/"+child.getName(), refs);
}
}
return count;
@ -5461,7 +5465,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue());
} else {
sd = lookupProfileReference(errors, element, stack, i, profile, sd);
sd = lookupProfileReference(valContext, errors, element, stack, i, profile, sd);
}
}
if (sd != null) {
@ -5528,7 +5532,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private StructureDefinition lookupProfileReference(List<ValidationMessage> errors, Element element, NodeStack stack,
private StructureDefinition lookupProfileReference(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack,
int i, Element profile, StructureDefinition sd) {
String url = profile.primitiveValue();
CanonicalResourceLookupResult cr = crLookups.get(url);
@ -5540,7 +5544,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
} else {
try {
sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, url);
sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, valContext.getAppContext(), url);
crLookups.put(url, new CanonicalResourceLookupResult(sd));
} catch (Exception e) {
if (STACK_TRACE) { e.printStackTrace(); }
@ -5689,7 +5693,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (element.getType().equals("CodeSystem")) {
return new CodeSystemValidator(this).validateCodeSystem(valContext, errors, element, stack, baseOptions.withLanguage(stack.getWorkingLang())) && ok;
} else if (element.getType().equals("ConceptMap")) {
return new ConceptMapValidator(this).validateConceptMap(errors, element, stack, baseOptions.withLanguage(stack.getWorkingLang())) && ok;
return new ConceptMapValidator(this).validateConceptMap(valContext, errors, element, stack, baseOptions.withLanguage(stack.getWorkingLang())) && ok;
} else if (element.getType().equals("SearchParameter")) {
return new SearchParameterValidator(this, fpe).validateSearchParameter(errors, element, stack) && ok;
} else if (element.getType().equals("StructureDefinition")) {
@ -5697,7 +5701,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (element.getType().equals("StructureMap")) {
return new StructureMapValidator(this, fpe, profileUtilities).validateStructureMap(errors, element, stack) && ok;
} else if (element.getType().equals("ValueSet")) {
return new ValueSetValidator(this).validateValueSet(errors, element, stack) && ok;
return new ValueSetValidator(this).validateValueSet(valContext, errors, element, stack) && ok;
} else if ("http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/ViewDefinition".equals(element.getProperty().getStructure().getUrl())) {
if (element.getNativeObject() != null && element.getNativeObject() instanceof JsonObject) {
JsonObject json = (JsonObject) element.getNativeObject();
@ -7726,4 +7730,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return this;
}
public IValidatorResourceFetcher getFetcher() {
return this.fetcher;
}
public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
this.fetcher = value;
return this;
}
}

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.CodeSystem;
@ -17,6 +18,7 @@ import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
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;
@ -24,6 +26,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
public class ConceptMapValidator extends BaseValidator {
@ -109,7 +112,7 @@ public class ConceptMapValidator extends BaseValidator {
super(parent);
}
public boolean validateConceptMap(List<ValidationMessage> errors, Element cm, NodeStack stack, ValidationOptions options) {
public boolean validateConceptMap(ValidationContext valContext, List<ValidationMessage> errors, Element cm, NodeStack stack, ValidationOptions options) {
boolean ok = true;
Map<String, PropertyDefinition> props = new HashMap<>();
Map<String, String> attribs = new HashMap<>();
@ -147,7 +150,7 @@ public class ConceptMapValidator extends BaseValidator {
List<Element> groups = cm.getChildrenByName("group");
int ci = 0;
for (Element group : groups) {
ok = validateGroup(errors, group, stack.push(group, ci, null, null), props, attribs, options, sourceScope, targetScope) && ok;
ok = validateGroup(valContext, errors, group, stack.push(group, ci, null, null), props, attribs, options, sourceScope, targetScope) && ok;
ci++;
}
@ -213,7 +216,7 @@ public class ConceptMapValidator extends BaseValidator {
return null;
}
private boolean validateGroup(List<ValidationMessage> errors, Element grp, NodeStack stack, Map<String, PropertyDefinition> props, Map<String, String> attribs, ValidationOptions options, VSReference sourceScope, VSReference targetScope) {
private boolean validateGroup(ValidationContext valContext, List<ValidationMessage> errors, Element grp, NodeStack stack, Map<String, PropertyDefinition> props, Map<String, String> attribs, ValidationOptions options, VSReference sourceScope, VSReference targetScope) {
boolean ok = true;
GroupContext ctxt = new GroupContext();
ctxt.sourceScope = sourceScope;
@ -229,6 +232,11 @@ public class ConceptMapValidator extends BaseValidator {
} else {
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), sourceScope != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_UNKNOWN, e.getValue());
}
if (ctxt.source.version == null && ctxt.source.cs != null) {
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(null, valContext.getAppContext(), ctxt.source.url);
warning(errors, NO_RULE_DATE, IssueType.INVALID, grp.line(), grp.col(), stack.getLiteralPath(), possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
ctxt.source.url, ctxt.source.cs.getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
}
}
e = grp.getNamedChild("target", false);
if (warning(errors, "2023-03-05", IssueType.REQUIRED, grp.line(), grp.col(), stack.getLiteralPath(), e != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_MISSING)) {
@ -241,6 +249,11 @@ public class ConceptMapValidator extends BaseValidator {
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), targetScope != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_UNKNOWN, e.getValue());
}
if (ctxt.target.version == null && ctxt.target.cs != null) {
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(null, valContext.getAppContext(), ctxt.target.url);
warning(errors, NO_RULE_DATE, IssueType.INVALID, grp.line(), grp.col(), stack.getLiteralPath(), possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
ctxt.target.url, ctxt.target.cs.getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
}
}
List<Element> elements = grp.getChildrenByName("element");
int ci = 0;

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.validation.instance.type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.elementmodel.Element;
@ -11,6 +12,7 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
@ -23,6 +25,7 @@ import org.hl7.fhir.validation.codesystem.GeneralCodeSystemChecker;
import org.hl7.fhir.validation.codesystem.SnomedCTChecker;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
public class ValueSetValidator extends BaseValidator {
@ -57,13 +60,13 @@ public class ValueSetValidator extends BaseValidator {
super(parent);
}
public boolean validateValueSet(List<ValidationMessage> errors, Element vs, NodeStack stack) {
public boolean validateValueSet(ValidationContext valContext, List<ValidationMessage> errors, Element vs, NodeStack stack) {
boolean ok = true;
if (!VersionUtilities.isR2Ver(context.getVersion())) {
List<Element> composes = vs.getChildrenByName("compose");
int cc = 0;
for (Element compose : composes) {
ok = validateValueSetCompose(errors, compose, stack.push(compose, composes.size() > 1 ? cc : -1, null, null), vs.getNamedChildValue("url", false), "retired".equals(vs.getNamedChildValue("url", false)), vs) & ok;
ok = validateValueSetCompose(valContext, errors, compose, stack.push(compose, composes.size() > 1 ? cc : -1, null, null), vs.getNamedChildValue("url", false), "retired".equals(vs.getNamedChildValue("url", false)), vs) & ok;
cc++;
}
}
@ -99,24 +102,24 @@ public class ValueSetValidator extends BaseValidator {
}
private boolean validateValueSetCompose(List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
private boolean validateValueSetCompose(ValidationContext valContext, List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
boolean ok = true;
List<Element> includes = compose.getChildrenByName("include");
int ci = 0;
for (Element include : includes) {
ok = validateValueSetInclude(errors, include, stack.push(include, ci, null, null), vsid, retired, vsSrc) && ok;
ok = validateValueSetInclude(valContext, errors, include, stack.push(include, ci, null, null), vsid, retired, vsSrc) && ok;
ci++;
}
List<Element> excludes = compose.getChildrenByName("exclude");
int ce = 0;
for (Element exclude : excludes) {
ok = validateValueSetInclude(errors, exclude, stack.push(exclude, ce, null, null), vsid, retired, vsSrc) && ok;
ok = validateValueSetInclude(valContext, errors, exclude, stack.push(exclude, ce, null, null), vsid, retired, vsSrc) && ok;
ce++;
}
return ok;
}
private boolean validateValueSetInclude(List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
private boolean validateValueSetInclude(ValidationContext valContext, List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
boolean ok = true;
String system = include.getChildValue("system");
String version = include.getChildValue("version");
@ -163,6 +166,14 @@ public class ValueSetValidator extends BaseValidator {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), cs.size() == 1, version == null ? I18nConstants.VALUESET_INCLUDE_CS_MULTI_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_MULTI_FOUND, system, version) && ok;
}
}
if (version == null) {
CodeSystem cs = context.fetchCodeSystem(system);
if (cs != null) {
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(null, valContext.getAppContext(), system);
warning(errors, NO_RULE_DATE, IssueType.INVALID, include.line(), include.col(), stack.getLiteralPath(), possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
system, cs.getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
}
}
}
List<Element> concepts = include.getChildrenByName("concept");
List<Element> filters = include.getChildrenByName("filter");

View File

@ -449,7 +449,7 @@ public class R4R5MapTester implements IValidatorResourceFetcher {
}
@Override
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, String url) throws URISyntaxException {
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, Object appContext, String url) throws URISyntaxException {
return null;
}
@ -458,4 +458,9 @@ public class R4R5MapTester implements IValidatorResourceFetcher {
return false;
}
@Override
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();
}
}

View File

@ -13,9 +13,11 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.NotImplementedException;
@ -832,7 +834,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
@Override
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, String url) {
public CanonicalResource fetchCanonicalResource(IResourceValidator validator, Object appContext, String url) {
return null;
}
@ -867,4 +869,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
StructureDefinition structure, ElementDefinition element, String path) {
return EnumSet.allOf(ElementValidationAction.class);
}
@Override
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();
}
}