Add tests

This commit is contained in:
James Agnew 2017-12-22 13:33:35 -05:00
parent 19863de012
commit 8c10d96416
8 changed files with 978 additions and 715 deletions

View File

@ -1,8 +1,17 @@
package org.hl7.fhir.dstu3.conformance;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
@ -11,15 +20,50 @@ import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.dstu3.elementmodel.ObjectConverter;
import org.hl7.fhir.dstu3.elementmodel.Property;
import org.hl7.fhir.dstu3.formats.IParser;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.ElementDefinition.*;
import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
import org.hl7.fhir.dstu3.formats.XmlParser;
import org.hl7.fhir.dstu3.model.Base;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Element;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.ElementDefinition.AggregationMode;
import org.hl7.fhir.dstu3.model.ElementDefinition.DiscriminatorType;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBaseComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionExampleComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
import org.hl7.fhir.dstu3.model.ElementDefinition.SlicingRules;
import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.dstu3.model.Enumeration;
import org.hl7.fhir.dstu3.model.Enumerations.BindingStrength;
import org.hl7.fhir.dstu3.model.StructureDefinition.*;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionDifferentialComponent;
import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent;
import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionSnapshotComponent;
import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.dstu3.utils.*;
import org.hl7.fhir.dstu3.utils.NarrativeGenerator;
import org.hl7.fhir.dstu3.utils.ToolingExtensions;
import org.hl7.fhir.dstu3.utils.TranslatingUtilities;
import org.hl7.fhir.dstu3.utils.formats.CSVWriter;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
@ -28,10 +72,15 @@ import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.*;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xml.SchematronWriter;
import org.hl7.fhir.utilities.xml.SchematronWriter.*;
import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
/**
* This class provides a set of utility operations for working with Profiles.
@ -208,36 +257,46 @@ public class ProfileUtilities extends TranslatingUtilities {
* @param path The path of the element within the structure to get the children for
* @return A List containing the element children (all of them are Elements)
*/
public static List<ElementDefinition> getChildList(StructureDefinition profile, String path) {
public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
List<ElementDefinition> res = new ArrayList<ElementDefinition>();
for (ElementDefinition e : profile.getSnapshot().getElement())
{
String p = e.getPath();
if (!Utilities.noString(e.getContentReference()) && path.startsWith(p))
{
if (path.length() > p.length())
return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1));
else
return getChildList(profile, e.getContentReference());
boolean capturing = id==null;
if (id==null && !path.contains("."))
capturing = true;
for (ElementDefinition e : profile.getSnapshot().getElement()) {
if (!capturing && id!=null && e.getId().equals(id)) {
capturing = true;
}
else if (p.startsWith(path+".") && !p.equals(path))
{
// If our element is a slice, stop capturing children as soon as we see the next slice
if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
break;
if (capturing) {
String p = e.getPath();
if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
if (path.length() > p.length())
return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null);
else
return getChildList(profile, e.getContentReference(), null);
} else if (p.startsWith(path+".") && !p.equals(path)) {
String tail = p.substring(path.length()+1);
if (!tail.contains(".")) {
res.add(e);
}
}
}
}
return res;
}
public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
return getChildList(structure, element.getPath());
return getChildList(structure, element.getPath(), element.getId());
}
public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
@ -319,9 +378,9 @@ public class ProfileUtilities extends TranslatingUtilities {
System.out.println(" "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId());
throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
// System.out.println("**BAD Differential element: " + profileName + ":" + e.getId());
}
}
}
}
private String sliceSummary(ElementDefinition ed) {
if (!ed.hasSlicing() && !ed.hasSliceName())
@ -451,7 +510,7 @@ public class ProfileUtilities extends TranslatingUtilities {
res = outcome;
updateFromBase(outcome, currentBase);
if (diffMatches.get(0).hasSliceName())
outcome.setSliceName(diffMatches.get(0).getSliceName());
outcome.setSliceName(diffMatches.get(0).getSliceName());
outcome.setSlicing(null);
updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*")) // if the base profile allows multiple types, but the profile only allows one, rename it
@ -485,15 +544,15 @@ public class ProfileUtilities extends TranslatingUtilities {
nbl++;
processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
} else {
StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
if (dt == null)
throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
contextName = dt.getUrl();
processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
if (dt == null)
throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
contextName = dt.getUrl();
processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
}
}
}
}
} else {
// ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
@ -512,29 +571,29 @@ public class ProfileUtilities extends TranslatingUtilities {
processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true).setSlicing(diffMatches.get(0).getSlicing());
start++;
} else {
// we're just going to accept the differential slicing at face value
ElementDefinition outcome = updateURLs(url, currentBase.copy());
outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
updateFromBase(outcome, currentBase);
// we're just going to accept the differential slicing at face value
ElementDefinition outcome = updateURLs(url, currentBase.copy());
outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
updateFromBase(outcome, currentBase);
if (!diffMatches.get(0).hasSlicing())
outcome.setSlicing(makeExtensionSlicing());
else
outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
if (!outcome.getPath().startsWith(resultPathBase))
throw new DefinitionException("Adding wrong path");
result.getElement().add(outcome);
if (!diffMatches.get(0).hasSlicing())
outcome.setSlicing(makeExtensionSlicing());
else
outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
if (!outcome.getPath().startsWith(resultPathBase))
throw new DefinitionException("Adding wrong path");
result.getElement().add(outcome);
// differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice.
if (!diffMatches.get(0).hasSliceName()) {
updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
// differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice.
if (!diffMatches.get(0).hasSliceName()) {
updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
if (!outcome.hasContentReference() && !outcome.hasType()) {
throw new DefinitionException("not done yet");
}
throw new DefinitionException("not done yet");
}
start++;
// result.getElement().remove(result.getElement().size()-1);
} else
checkExtensionDoco(outcome);
} else
checkExtensionDoco(outcome);
}
// now, for each entry in the diff matches, we're going to process the base item
// our processing scope for base is all the children of the current path
@ -610,7 +669,7 @@ public class ProfileUtilities extends TranslatingUtilities {
result.getElement().add(outcome);
if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
diffpos++;
diffpos++;
}
// now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
@ -648,6 +707,8 @@ public class ProfileUtilities extends TranslatingUtilities {
result.getElement().add(outcome);
baseCursor++;
}
//Lloyd - add this for test T15
baseCursor--;
}
}
// finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
@ -717,7 +778,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
}
int i = 0;
for (ElementDefinition e : result.getElement()) {
i++;
@ -1733,7 +1794,7 @@ public class ProfileUtilities extends TranslatingUtilities {
c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
}
}
} else if (t.hasProfile()) { // a profiled type
} else if (t.hasProfile() && (!t.getCode().equals("Extension") || t.getProfile().contains(":"))) { // a profiled type
String ref;
ref = pkp.getLinkForProfile(profile, t.getProfile());
if (ref != null) {
@ -1772,9 +1833,8 @@ public class ProfileUtilities extends TranslatingUtilities {
case BUNDLED : return "b";
case CONTAINED : return "c";
case REFERENCED: return "r";
default: return "?";
}
return "?";
}
@ -1971,14 +2031,6 @@ public class ProfileUtilities extends TranslatingUtilities {
row.getCells().add(left);
Cell gc = gen.new Cell();
row.getCells().add(gc);
if (element != null && element.getIsModifier())
checkForNoChange(element.getIsModifierElement(), gc.addImage(imagePath+"modifier.png", translate("sd.table", "This element is a modifier element"), "?!", null, null));
if (element != null && element.getMustSupport())
checkForNoChange(element.getMustSupportElement(), gc.addImage(imagePath+"mustsupport.png", translate("sd.table", "This element must be supported"), "S", "white", "red"));
if (element != null && element.getIsSummary())
checkForNoChange(element.getIsSummaryElement(), gc.addImage(imagePath+"summary.png", translate("sd.table", "This element is included in summaries"), "Σ", null, null));
if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
gc.addImage(imagePath+"lock.png", translate("sd.table", "This element has or is affected by some invariants"), "I", null, null);
ExtensionContext extDefn = null;
if (ext) {
@ -2190,20 +2242,6 @@ public class ProfileUtilities extends TranslatingUtilities {
return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null);
}
public static String pathReverse(String... args) {
StringBuilder s = new StringBuilder();
boolean d = false;
for(String arg: args) {
if (!d)
d = !Utilities.noString(arg);
else if (!s.toString().endsWith("/"))
s.append("/");
s.append(arg);
}
return s.toString();
}
private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn) throws IOException {
Cell c = gen.new Cell();
row.getCells().add(c);
@ -2233,7 +2271,7 @@ public class ProfileUtilities extends TranslatingUtilities {
if (definition != null && definition.hasShort()) {
if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
} else if (fallback != null && fallback.hasShort()) {
} else if (fallback != null && fallback != null && fallback.hasShort()) {
if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
}
@ -2245,7 +2283,7 @@ public class ProfileUtilities extends TranslatingUtilities {
if (ed != null) {
String p = ed.getUserString("path");
if (p != null) {
ref = p.startsWith("http:") || igmode ? p : pathReverse(corePath, p);
ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
}
}
c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
@ -2401,7 +2439,7 @@ public class ProfileUtilities extends TranslatingUtilities {
if (ed != null) {
String p = ed.getUserString("path");
if (p != null) {
ref = p.startsWith("http:") || igmode ? p : pathReverse(corePath, p);
ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
}
}
c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
@ -2700,7 +2738,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
return i;
}
}
if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
actual = base+(snapshot.get(i).getContentReference().substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
i = 0;
@ -3585,7 +3623,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType profile, String str) {
public static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType profile, String str) {
return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(Utilities.noString(str)? "$this" : str);
}
@ -3595,6 +3633,7 @@ public class ProfileUtilities extends TranslatingUtilities {
case PROFILE: return t.getPath()+"/@profile";
case TYPE: return t.getPath()+"/@type";
case VALUE: return t.getPath();
case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0
default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");
}
}

View File

@ -25,7 +25,7 @@ public class ValidationSupportChain implements IValidationSupport {
* Constructor
*/
public ValidationSupportChain() {
myChain = new ArrayList<IValidationSupport>();
myChain = new ArrayList<>();
}
/**

View File

@ -10,12 +10,18 @@ import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.*;
import ca.uhn.fhir.validation.schematron.SchematronBaseValidator;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Condition.ConditionClinicalStatus;
import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus;
import org.hl7.fhir.dstu3.model.EligibilityResponse.BenefitComponent;
import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
@ -166,6 +172,43 @@ public class ResourceValidatorDstu3Test {
assertEquals(0, output.getMessages().size());
}
@Test
@Ignore
public void testValidateProfileWithExtension() throws IOException, FHIRException {
PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport();
DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport();
ValidationSupportChain support = new ValidationSupportChain(valSupport, defaultSupport);
// Prepopulate SDs
valSupport.addStructureDefinition(loadStructureDefinition(defaultSupport, "/dstu3/myconsent-profile.xml"));
valSupport.addStructureDefinition(loadStructureDefinition(defaultSupport, "/dstu3/myconsent-ext.xml"));
FhirValidator val = ourCtx.newValidator();
val.registerValidatorModule(new FhirInstanceValidator(support));
Consent input = ourCtx.newJsonParser().parseResource(Consent.class, IOUtils.toString(ResourceValidatorDstu3Test.class.getResourceAsStream("/dstu3/myconsent-resource.json")));
ValidationResult output = val.validateWithResult(input);
List<SingleValidationMessage> all = logResultsAndReturnNonInformationalOnes(output);
assertEquals(0, all.size());
assertEquals(0, output.getMessages().size());
}
private StructureDefinition loadStructureDefinition(DefaultProfileValidationSupport theDefaultValSupport, String theResName) throws IOException, FHIRException {
StructureDefinition derived = ourCtx.newXmlParser().parseResource(StructureDefinition.class, IOUtils.toString(ResourceValidatorDstu3Test.class.getResourceAsStream(theResName)));
StructureDefinition base = theDefaultValSupport.fetchStructureDefinition(ourCtx, derived.getBaseDefinition());
Validate.notNull(base);
IWorkerContext worker = new HapiWorkerContext(ourCtx, theDefaultValSupport);
List<ValidationMessage> issues = new ArrayList<>();
ProfileUtilities profileUtilities = new ProfileUtilities(worker, issues, null);
profileUtilities.generateSnapshot(base, derived, "", "");
return derived;
}
@Test
@Ignore
public void testValidateDifferentPropertyButSameStartsWithPath() throws Exception {

View File

@ -22,8 +22,13 @@ import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidatorDstu3Test;
import org.hl7.fhir.dstu3.hapi.validation.ResourceValidatorDstu3Test;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.*;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport.CodeValidationResult;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
@ -35,6 +40,7 @@ import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.junit.*;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
@ -845,4 +851,50 @@ public class FhirInstanceValidatorR4Test {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testValidateProfileWithExtension() throws IOException, FHIRException {
PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport();
DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport();
ValidationSupportChain support = new ValidationSupportChain(valSupport, defaultSupport);
// Prepopulate SDs
valSupport.addStructureDefinition(loadStructureDefinition(defaultSupport, "/dstu3/myconsent-profile.xml"));
valSupport.addStructureDefinition(loadStructureDefinition(defaultSupport, "/dstu3/myconsent-ext.xml"));
FhirValidator val = ourCtx.newValidator();
val.registerValidatorModule(new FhirInstanceValidator(support));
Consent input = ourCtx.newJsonParser().parseResource(Consent.class, IOUtils.toString(ResourceValidatorDstu3Test.class.getResourceAsStream("/dstu3/myconsent-resource.json")));
input.getPolicyRule().addCoding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("EMRGONLY");
// Should pass
ValidationResult output = val.validateWithResult(input);
List<SingleValidationMessage> all = logResultsAndReturnNonInformationalOnes(output);
assertEquals(0, all.size());
assertEquals(0, output.getMessages().size());
// Now with the wrong datatype
input.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/PruebaExtension").get(0).setValue(new CodeType("AAA"));
// Should fail
output = val.validateWithResult(input);
all = logResultsAndReturnNonInformationalOnes(output);
assertThat(all.toString(), containsString("definition allows for the types [string] but found type code"));
}
private StructureDefinition loadStructureDefinition(DefaultProfileValidationSupport theDefaultValSupport, String theResName) throws IOException, FHIRException {
StructureDefinition derived = ourCtx.newXmlParser().parseResource(StructureDefinition.class, IOUtils.toString(ResourceValidatorDstu3Test.class.getResourceAsStream(theResName)));
StructureDefinition base = theDefaultValSupport.fetchStructureDefinition(ourCtx, derived.getBaseDefinition());
Validate.notNull(base);
IWorkerContext worker = new HapiWorkerContext(ourCtx, theDefaultValSupport);
List<ValidationMessage> issues = new ArrayList<>();
ProfileUtilities profileUtilities = new ProfileUtilities(worker, issues, null);
profileUtilities.generateSnapshot(base, derived, "", "");
return derived;
}
}

View File

@ -0,0 +1,32 @@
<StructureDefinition xmlns="http://hl7.org/fhir">
<id value="PruebaExtension" />
<meta>
<lastUpdated value="2017-12-19T16:53:11.296+01:00" />
</meta>
<url value="http://hl7.org/fhir/StructureDefinition/PruebaExtension" />
<name value="PruebaExtension" />
<status value="draft" />
<date value="2017-12-19T16:51:07.089+01:00" />
<fhirVersion value="3.0.1" />
<kind value="complex-type" />
<abstract value="false" />
<contextType value="resource" />
<context value="Consent" />
<type value="Extension" />
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/Extension" />
<derivation value="constraint" />
<differential>
<element id="Extension.url">
<path value="Extension.url" />
<fixedUri value="http://hl7.org/fhir/StructureDefinition/PruebaExtension" />
</element>
<element id="Extension.value[x]:valueString">
<path value="Extension.valueString" />
<sliceName value="valueString" />
<min value="1" />
<type>
<code value="string" />
</type>
</element>
</differential>
</StructureDefinition>

View File

@ -0,0 +1,42 @@
<StructureDefinition xmlns="http://hl7.org/fhir">
<id value="Consent" />
<meta>
<lastUpdated value="2017-12-21T17:22:27.087+01:00" />
</meta>
<url value="http://hl7.org/fhir/StructureDefinition/MyConsent" />
<name value="Consent" />
<status value="draft" />
<date value="2017-12-19T09:08:41.006+01:00" />
<description value="prueba&#xD;&#xA;" />
<fhirVersion value="3.0.1" />
<kind value="resource" />
<abstract value="false" />
<type value="Consent" />
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/Consent" />
<derivation value="constraint" />
<differential>
<element id="Consent.extension">
<path value="Consent.extension" />
<slicing>
<discriminator>
<type value="value" />
<path value="url" />
</discriminator>
<rules value="open" />
</slicing>
</element>
<element id="Consent.extension:pruebaExtension">
<path value="Consent.extension" />
<sliceName value="pruebaExtension" />
<min value="1" />
<type>
<code value="Extension" />
<profile value="http://hl7.org/fhir/StructureDefinition/PruebaExtension" />
</type>
</element>
<element id="Consent.identifier">
<path value="Consent.identifier" />
<min value="1" />
</element>
</differential>
</StructureDefinition>

View File

@ -0,0 +1,55 @@
{
"resourceType": "Consent",
"id": "consent-example-basic",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n <p>\n\tAuthorize Normal access for Treatment\n\t</p><p>\n Patient &quot;P. van de Heuvel&quot; wishes to have all of the PHI collected at the Good Health Psychiatric Hospital \n available for normal treatment use.\n </p>\n </div>"
},
"status": "active",
"patient": {
"reference": "Patient/f001",
"display": "P. van de Heuvel"
},
"period": {
"start": "1964-01-01",
"end": "2016-01-01"
},
"dateTime": "2016-05-11",
"organization": [
{
"reference": "Organization/Infoway",
"display": "Canada Infoway"
}
],
"sourceAttachment": {
"title": "The terms of the consent in lawyer speak."
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/v2/0203",
"code": "MR"
}
]
},
"system": "urn:oid:1.2.36.146.595.217.0.1",
"value": "12345",
"period": {
"start": "2001-05-06"
},
"assigner": {
"display": "Acme Healthcare"
}
}
],
"extension":[
{
"url" : "http://hl7.org/fhir/StructureDefinition/PruebaExtension",
"valueString" : "123456789"
}
],
"policyRule": "http://goodhealth.org/consent/policy/opt-in"
}