3764 Fix NullPointer on validator meta extension (#3910)

* Failing test

* Failing test

* Add necessary package load to test

* WIP add minimum code for passing test

* Use PrePopulatedValidationSupport binaryMap in NpmPackage

* Rename test

* Add test for non-CachingValidationSupport

* Rename Tests + InvalidExtensionValue test

* Revert change to ValidateDirectory

* Gentle reformat

* Fix typo

* Test CachingValidationSupport fetchBinary

* Test for NpmPackageValidationSupport

* Refactor

* Check for nulls when adding Binary

* Test PrePopulatedValidationSupport addBinary

* Test ValidationSupportChain

* Move ValidationSupportChainTest to proper package

* Remove FIXME in VersionSpecificWorkerContextWrapper

* Test VersionSpecificWorkerContextWrapper

* Add example in docs

Co-authored-by: dotasek <david.otasek@smilecdr.com>
This commit is contained in:
dotasek 2022-08-12 15:01:07 -04:00 committed by GitHub
parent 64e1f4d381
commit dad1e420fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 531 additions and 64 deletions

View File

@ -255,6 +255,13 @@ public interface IValidationSupport {
return null;
}
/**
* Fetch the given binary data by key.
* @param binaryKey
* @return
*/
default byte[] fetchBinary(String binaryKey) { return null; }
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"

View File

@ -6,8 +6,8 @@
The following code can be used to generate a Snapshot Profile (StructureDefinition) when all you have is a differential.
```java
// Create a validation chain that includes default validation support and a
// snapshot generator
// Create a validation support chain that includes default validation support
// and a snapshot generator
DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport();
SnapshotGeneratingValidationSupport snapshotGenerator = new SnapshotGeneratingValidationSupport(myFhirCtx, defaultSupport);
ValidationSupportChain chain = new ValidationSupportChain(defaultSupport, snapshotGenerator);
@ -16,4 +16,28 @@ ValidationSupportChain chain = new ValidationSupportChain(defaultSupport, snapsh
StructureDefinition snapshot = chain.generateSnapshot(differential, "http://foo", null, "THE BEST PROFILE");
```
## Validate a Resource with Cross Version Extensions
The following code can be used to validate a resource using FHIR [Cross Version Extensions](http://hl7.org/fhir/versions.html#extensions).
Note that you must have the [hl7.fhir.xver-extensions-0.0.11.tgz](http://fhir.org/packages/hl7.fhir.xver-extensions/0.0.11/package.tgz) package available in your classpath.
```java
// Create a validation support chain that includes default validation support
// and support from the hl7.fhir.xver-extensions NPM pacakage.
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(myFhirCtx);
npmPackageSupport.loadPackageFromClasspath("classpath:package/hl7.fhir.xver-extensions-0.0.11.tgz");
myFhirCtx.setValidationSupport(new ValidationSupportChain(
new DefaultProfileValidationSupport(myFhirCtx),
npmPackageSupport
));
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myFhirCtx);
FhirValidator validator = myFhirCtx.newValidator();
validator.registerValidatorModule(instanceValidator);
// Validate theResource
ValidationResult validationResult = validator.validateWithResult(theResource);
```

View File

@ -56,6 +56,11 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
return myWrap.fetchResource(theClass, theUri);
}
@Override
public byte[] fetchBinary(String theBinaryKey) {
return myWrap.fetchBinary(theBinaryKey);
}
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
return myWrap.isCodeSystemSupported(theValidationSupportContext, theSystem);

View File

@ -47,7 +47,7 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
private final Cache<String, Object> myExpandValueSetCache;
/**
* Constuctor with default timeouts
* Constructor with default timeouts
*
* @param theWrap The validation support module to wrap
*/
@ -140,6 +140,11 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
return loadFromCache(myCache, "fetchStructureDefinition " + theUrl, t -> super.fetchStructureDefinition(theUrl));
}
@Override
public byte[] fetchBinary(String theBinaryKey) {
return loadFromCache(myCache, "fetchBinary " + theBinaryKey, t -> super.fetchBinary(theBinaryKey));
}
@Override
public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri,

View File

@ -4,13 +4,14 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.npm.NpmPackage;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
/**
@ -38,7 +39,14 @@ public class NpmPackageValidationSupport extends PrePopulatedValidationSupport {
try (InputStream is = ClasspathUtil.loadResourceAsStream(theClasspath)) {
NpmPackage pkg = NpmPackage.fromPackage(is);
if (pkg.getFolders().containsKey("package")) {
NpmPackage.NpmPackageFolder packageFolder = pkg.getFolders().get("package");
loadResourcesFromPackage(pkg);
loadBinariesFromPackage(pkg);
}
}
}
private void loadResourcesFromPackage(NpmPackage thePackage) {
NpmPackage.NpmPackageFolder packageFolder = thePackage.getFolders().get("package");
for (String nextFile : packageFolder.listFiles()) {
if (nextFile.toLowerCase(Locale.US).endsWith(".json")) {
@ -47,9 +55,12 @@ public class NpmPackageValidationSupport extends PrePopulatedValidationSupport {
super.addResource(resource);
}
}
}
}
}
private void loadBinariesFromPackage(NpmPackage thePackage) throws IOException {
List<String> binaries = thePackage.list("other");
for (String binaryName : binaries) {
addBinary(TextFile.streamToBytes(thePackage.load("other", binaryName)), binaryName);
}
}
}

View File

@ -34,15 +34,15 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
private final Map<String, IBaseResource> myCodeSystems;
private final Map<String, IBaseResource> myStructureDefinitions;
private final Map<String, IBaseResource> myValueSets;
private final Map<String, byte[]> myBinaries;
/**
* Constructor
*/
public PrePopulatedValidationSupport(FhirContext theContext) {
this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>());
this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
}
/**
* Constructor
*
@ -52,16 +52,49 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* the resource itself.
* @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are
* the resource itself.
**/
public PrePopulatedValidationSupport(
FhirContext theFhirContext,
Map<String, IBaseResource> theStructureDefinitions,
Map<String, IBaseResource> theValueSets,
Map<String, IBaseResource> theCodeSystems) {
this(theFhirContext, theStructureDefinitions, theValueSets, theCodeSystems, new HashMap<>());
}
/**
* Constructor
*
* @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and
* values are the resource itself.
* @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are
* the resource itself.
* @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are
* the resource itself.
* @param theBinaries The binary files to be returned by this module. Keys are the unique filename for the binary, and values
* are the contents of the file as a byte array.
*/
public PrePopulatedValidationSupport(FhirContext theFhirContext, Map<String, IBaseResource> theStructureDefinitions, Map<String, IBaseResource> theValueSets, Map<String, IBaseResource> theCodeSystems) {
public PrePopulatedValidationSupport(
FhirContext theFhirContext,
Map<String, IBaseResource> theStructureDefinitions,
Map<String, IBaseResource> theValueSets,
Map<String, IBaseResource> theCodeSystems,
Map<String, byte[]> theBinaries) {
super(theFhirContext);
Validate.notNull(theFhirContext, "theFhirContext must not be null");
Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null");
Validate.notNull(theValueSets, "theValueSets must not be null");
Validate.notNull(theCodeSystems, "theCodeSystems must not be null");
Validate.notNull(theBinaries, "theBinaries must not be null");
myStructureDefinitions = theStructureDefinitions;
myValueSets = theValueSets;
myCodeSystems = theCodeSystems;
myBinaries = theBinaries;
}
public void addBinary(byte[] theBinary, String theBinaryKey) {
Validate.notNull(theBinary, "theBinaryKey must not be null");
Validate.notNull(theBinary, "the" + theBinaryKey + " must not be null");
myBinaries.put(theBinaryKey, theBinary);
}
/**
@ -220,6 +253,9 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
return myStructureDefinitions.get(theUrl);
}
@Override
public byte[] fetchBinary(String theBinaryKey) { return myBinaries.get(theBinaryKey); }
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
return myCodeSystems.containsKey(theSystem);

View File

@ -257,6 +257,17 @@ public class ValidationSupportChain implements IValidationSupport {
return null;
}
@Override
public byte[] fetchBinary(String key) {
for (IValidationSupport next : myChain) {
byte[] retVal = next.fetchBinary(key);
if (retVal != null) {
return retVal;
}
}
return null;
}
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
for (IValidationSupport next : myChain) {

View File

@ -131,12 +131,12 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
@Override
public boolean hasBinaryKey(String s) {
throw new UnsupportedOperationException(Msg.code(2126));
return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null;
}
@Override
public byte[] getBinaryForKey(String s) {
throw new UnsupportedOperationException(Msg.code(2127));
return myValidationSupportContext.getRootValidationSupport().fetchBinary(s);
}
@Override
@ -258,7 +258,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
@Override
public void cacheResource(Resource res) {
throw new UnsupportedOperationException(Msg.code(660));
}
@Override

View File

@ -1,41 +0,0 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.i18n.Msg;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
public class ValidationSupportChainTest {
@Test
public void testVersionCheck() {
DefaultProfileValidationSupport ctx3 = new DefaultProfileValidationSupport(FhirContext.forDstu3());
DefaultProfileValidationSupport ctx4 = new DefaultProfileValidationSupport(FhirContext.forR4());
try {
new ValidationSupportChain(ctx3, ctx4);
} catch (ConfigurationException e) {
assertEquals(Msg.code(709) + "Trying to add validation support of version R4 to chain with 1 entries of version DSTU3", e.getMessage());
}
}
@Test
public void testMissingContext() {
IValidationSupport ctx = mock(IValidationSupport.class);
try {
new ValidationSupportChain(ctx);
} catch (ConfigurationException e) {
assertEquals(Msg.code(708) + "Can not add validation support: getFhirContext() returns null", e.getMessage());
}
}
}

View File

@ -18,6 +18,8 @@ import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@ -68,5 +70,20 @@ public class CachingValidationSupportTest {
assertEquals(1, responses.size());
}
@Test
public void fetchBinary_normally_accessesSuperOnlyOnce() {
final byte[] EXPECTED_BINARY = "dummyBinaryContent".getBytes();
final String EXPECTED_BINARY_KEY = "dummyBinaryKey";
when(myValidationSupport.getFhirContext()).thenReturn(ourCtx);
when(myValidationSupport.fetchBinary(EXPECTED_BINARY_KEY)).thenReturn(EXPECTED_BINARY);
CachingValidationSupport support = new CachingValidationSupport(myValidationSupport);
final byte[] firstActualBinary = support.fetchBinary(EXPECTED_BINARY_KEY);
assertEquals(EXPECTED_BINARY,firstActualBinary);
verify(myValidationSupport, times(1)).fetchBinary(EXPECTED_BINARY_KEY);
final byte[] secondActualBinary = support.fetchBinary(EXPECTED_BINARY_KEY);
assertEquals(EXPECTED_BINARY,secondActualBinary);
verify(myValidationSupport, times(1)).fetchBinary(EXPECTED_BINARY_KEY);
}
}

View File

@ -6,6 +6,9 @@ import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
public class PrePopulatedValidationSupportTest {
@ -33,4 +36,19 @@ public class PrePopulatedValidationSupportTest {
}
@Test
public void testAddBinary() {
final Map<String, byte[]> EXPECTED_BINARIES_MAP = Map.of(
"dummyBinary1.txt", "myDummyContent1".getBytes(),
"dummyBinary2.txt", "myDummyContent2".getBytes()
);
for (Map.Entry<String,byte[]> entry : EXPECTED_BINARIES_MAP.entrySet()) {
mySvc.addBinary(entry.getValue(),entry.getKey());
}
for (Map.Entry<String,byte[]> entry : EXPECTED_BINARIES_MAP.entrySet()) {
assertArrayEquals(entry.getValue(), mySvc.fetchBinary(entry.getKey()));
}
}
}

View File

@ -0,0 +1,81 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.i18n.Msg;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ValidationSupportChainTest {
@Test
public void testVersionCheck() {
DefaultProfileValidationSupport ctx3 = new DefaultProfileValidationSupport(FhirContext.forDstu3());
DefaultProfileValidationSupport ctx4 = new DefaultProfileValidationSupport(FhirContext.forR4());
try {
new ValidationSupportChain(ctx3, ctx4);
} catch (ConfigurationException e) {
assertEquals(Msg.code(709) + "Trying to add validation support of version R4 to chain with 1 entries of version DSTU3", e.getMessage());
}
}
@Test
public void testMissingContext() {
IValidationSupport ctx = mock(IValidationSupport.class);
try {
new ValidationSupportChain(ctx);
} catch (ConfigurationException e) {
assertEquals(Msg.code(708) + "Can not add validation support: getFhirContext() returns null", e.getMessage());
}
}
@Test
public void fetchBinary_normally_returnsExpectedBinaries() {
final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes();
final byte[] EXPECTED_BINARY_CONTENT_2 = "dummyBinaryContent2".getBytes();
final String EXPECTED_BINARY_KEY_1 = "dummyBinaryKey1";
final String EXPECTED_BINARY_KEY_2 = "dummyBinaryKey2";
IValidationSupport validationSupport1 = createMockValidationSupportWithSingleBinary(EXPECTED_BINARY_KEY_1, EXPECTED_BINARY_CONTENT_1);
IValidationSupport validationSupport2 = createMockValidationSupportWithSingleBinary(EXPECTED_BINARY_KEY_2, EXPECTED_BINARY_CONTENT_2);
ValidationSupportChain validationSupportChain = new ValidationSupportChain(validationSupport1, validationSupport2);
final byte[] actualBinaryContent1 = validationSupportChain.fetchBinary(EXPECTED_BINARY_KEY_1 );
final byte[] actualBinaryContent2 = validationSupportChain.fetchBinary(EXPECTED_BINARY_KEY_2 );
assertArrayEquals(EXPECTED_BINARY_CONTENT_1, actualBinaryContent1);
assertArrayEquals(EXPECTED_BINARY_CONTENT_2, actualBinaryContent2);
assertNull(validationSupportChain.fetchBinary("nonExistentKey"));
}
private static IValidationSupport createMockValidationSupport() {
IValidationSupport validationSupport;
validationSupport = mock(IValidationSupport.class);
FhirContext mockContext = mock(FhirContext.class);
when(mockContext.getVersion()).thenReturn(FhirVersionEnum.R4.getVersionImplementation());
when(validationSupport.getFhirContext()).thenReturn(mockContext);
return validationSupport;
}
private static IValidationSupport createMockValidationSupportWithSingleBinary(String expected_binary_key, byte[] expected_binary_content) {
IValidationSupport validationSupport1 = createMockValidationSupport();
when(validationSupport1.fetchBinary(expected_binary_key)).thenReturn(expected_binary_content);
return validationSupport1;
}
}

View File

@ -0,0 +1,97 @@
package org.hl7.fhir.common.hapi.validation.validator;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.i18n.HapiLocalizer;
import org.hl7.fhir.r5.model.Resource;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class VersionSpecificWorkerContextWrapperTest {
final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes();
final byte[] EXPECTED_BINARY_CONTENT_2 = "dummyBinaryContent2".getBytes();
final String EXPECTED_BINARY_KEY_1 = "dummyBinaryKey1";
final String EXPECTED_BINARY_KEY_2 = "dummyBinaryKey2";
final String NON_EXISTENT_BINARY_KEY = "nonExistentBinaryKey";
@Test
public void hasBinaryKey_normally_returnsExpected() {
IValidationSupport validationSupport = mockValidationSupportWithTwoBinaries();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = mock(VersionSpecificWorkerContextWrapper.IVersionTypeConverter.class);
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, converter);
assertTrue(wrapper.hasBinaryKey(EXPECTED_BINARY_KEY_1), "wrapper should have binary key " + EXPECTED_BINARY_KEY_1 );
assertTrue(wrapper.hasBinaryKey(EXPECTED_BINARY_KEY_2), "wrapper should have binary key " + EXPECTED_BINARY_KEY_1 );
assertFalse(wrapper.hasBinaryKey(NON_EXISTENT_BINARY_KEY), "wrapper should not have binary key " + NON_EXISTENT_BINARY_KEY);
}
@Test
public void getBinaryForKey_normally_returnsExpected() {
IValidationSupport validationSupport = mockValidationSupportWithTwoBinaries();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = mock(VersionSpecificWorkerContextWrapper.IVersionTypeConverter.class);
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, converter);
assertArrayEquals(EXPECTED_BINARY_CONTENT_1, wrapper.getBinaryForKey(EXPECTED_BINARY_KEY_1));
assertArrayEquals(EXPECTED_BINARY_CONTENT_2, wrapper.getBinaryForKey(EXPECTED_BINARY_KEY_2));
assertNull(wrapper.getBinaryForKey(NON_EXISTENT_BINARY_KEY), "wrapper should return null for binary key " + NON_EXISTENT_BINARY_KEY);
}
@Test
public void cacheResource_normally_executesWithoutException() {
IValidationSupport validationSupport = mockValidationSupport();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = mock(VersionSpecificWorkerContextWrapper.IVersionTypeConverter.class);
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, converter);
wrapper.cacheResource(mock(Resource.class));
}
private IValidationSupport mockValidationSupportWithTwoBinaries() {
IValidationSupport validationSupport;
validationSupport = mockValidationSupport();
when(validationSupport.fetchBinary(EXPECTED_BINARY_KEY_1)).thenReturn(EXPECTED_BINARY_CONTENT_1);
when(validationSupport.fetchBinary(EXPECTED_BINARY_KEY_2)).thenReturn(EXPECTED_BINARY_CONTENT_2);
return validationSupport;
}
private static ValidationSupportContext mockValidationSupportContext(IValidationSupport validationSupport) {
ValidationSupportContext mockContext;
mockContext = mock(ValidationSupportContext.class);
when(mockContext.getRootValidationSupport()).thenReturn(validationSupport);
return mockContext;
}
private static IValidationSupport mockValidationSupport() {
IValidationSupport mockValidationSupport;
mockValidationSupport = mock(IValidationSupport.class);
FhirContext mockFhirContext = mock(FhirContext.class);
when(mockFhirContext.getLocalizer()).thenReturn(new HapiLocalizer());
when(mockFhirContext.getVersion()).thenReturn(FhirVersionEnum.R4.getVersionImplementation());
when(mockValidationSupport.getFhirContext()).thenReturn(mockFhirContext);
return mockValidationSupport;
}
}

View File

@ -12,27 +12,34 @@ import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationS
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r4.model.Patient;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class NpmPackageValidationSupportTest {
private static final Logger ourLog = LoggerFactory.getLogger(NpmPackageValidationSupportTest.class);
private FhirContext myFhirContext = FhirContext.forR4Cached();
private Map<String, byte[]> EXPECTED_BINARIES_MAP = Map.of(
"dummyBinary1.txt", "myDummyContent1".getBytes(),
"dummyBinary2.txt", "myDummyContent2".getBytes()
);
@Test
public void testValidateWithPackage() throws IOException {
// Create an NPM Package Support module and load one package in from
// the classpath
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(myFhirContext);
npmPackageSupport.loadPackageFromClasspath("classpath:package/UK.Core.r4-1.1.0.tgz");
NpmPackageValidationSupport npmPackageSupport = getNpmPackageValidationSupport("classpath:package/UK.Core.r4-1.1.0.tgz");
// Create a support chain including the NPM Package Support
ValidationSupportChain validationSupportChain = new ValidationSupportChain(
@ -64,4 +71,21 @@ public class NpmPackageValidationSupportTest {
}
@NotNull
private NpmPackageValidationSupport getNpmPackageValidationSupport(String theClasspath) throws IOException {
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(myFhirContext);
npmPackageSupport.loadPackageFromClasspath(theClasspath);
return npmPackageSupport;
}
@Test
public void loadPackageFromClasspath_normally_loadsExpectedBinaries() throws IOException {
NpmPackageValidationSupport npmPackageSupport = getNpmPackageValidationSupport("classpath:package/dummy-package-with-binaries.tgz");
for (Map.Entry<String, byte[]> entry : EXPECTED_BINARIES_MAP.entrySet()) {
byte[] expectedBytes = entry.getValue();
byte[] actualBytes = npmPackageSupport.fetchBinary(entry.getKey());
assertArrayEquals(expectedBytes, actualBytes);
}
}
}

View File

@ -0,0 +1,35 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r4.model.MedicationRequest;
import org.junit.jupiter.api.Test;
import static ca.uhn.fhir.util.ClasspathUtil.loadResource;
public class ParserWithValidationR4Test {
private static final FhirContext ourCtx = FhirContext.forR4();
@Test
public void testActivityDefinitionElementsOrder() {
ourCtx.setValidationSupport(getValidationSupport());
MedicationRequest med_req = ourCtx.newJsonParser().parseResource(MedicationRequest.class, loadResource("/r4/amz/medication-request-amz.json"));
final FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ourCtx);
instanceValidator.setNoTerminologyChecks(true);
FhirValidator validator = ourCtx.newValidator();
validator.registerValidatorModule(instanceValidator);
ValidationResult validationResult = validator.validateWithResult(med_req);
validationResult.getMessages().forEach(System.out::println);
}
private IValidationSupport getValidationSupport() {
return new ValidationSupportChain(new DefaultProfileValidationSupport(ourCtx));
}
}

View File

@ -0,0 +1,99 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.MedicationRequest;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.function.Predicate;
import static ca.uhn.fhir.util.ClasspathUtil.loadResource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class XverExtensionsValidationTest {
private static final FhirContext ourCtx = FhirContext.forR4();
@Test
public void validation_XverExtensionsAndCachingValidationSupport_ReturnsNoErrors() throws IOException {
ourCtx.setValidationSupport(new CachingValidationSupport(getValidationSupport()));
MedicationRequest med_req;
med_req = getMedicationRequest();
FhirValidator validator = getFhirValidator();
ValidationResult validationResult = validator.validateWithResult(med_req);
assertEquals(0, validationResult.getMessages().stream().filter(errorMessagePredicate()).count());
}
@Test
public void validation_XverExtensions_ReturnsNoErrors() throws IOException {
ourCtx.setValidationSupport(getValidationSupport());
MedicationRequest med_req = getMedicationRequest();
FhirValidator validator = getFhirValidator();
ValidationResult validationResult = validator.validateWithResult(med_req);
assertEquals(0, validationResult.getMessages().stream().filter(errorMessagePredicate()).count());
}
@Test
public void validation_InvalidExtensionValue_ReturnsError() throws IOException {
ourCtx.setValidationSupport(getValidationSupport());
MedicationRequest med_req = getMedicationRequest();
med_req.getMeta().getExtension().get(0).setValue(new IntegerType().setValue(123));
FhirValidator validator = getFhirValidator();
ValidationResult validationResult = validator.validateWithResult(med_req);
assertEquals(1, validationResult.getMessages().stream().filter(errorMessagePredicate()).count());
SingleValidationMessage errorMessage = validationResult.getMessages().stream().filter(errorMessagePredicate()).findFirst().get();
assertEquals("Extension_EXT_Type", errorMessage.getMessageId());
}
@NotNull
private static Predicate<SingleValidationMessage> errorMessagePredicate() {
return message -> message.getSeverity() == ResultSeverityEnum.ERROR;
}
@NotNull
private static FhirValidator getFhirValidator() {
FhirValidator validator;
final FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ourCtx);
instanceValidator.setNoTerminologyChecks(true);
validator = ourCtx.newValidator();
validator.registerValidatorModule(instanceValidator);
return validator;
}
@NotNull
private static MedicationRequest getMedicationRequest() {
MedicationRequest med_req;
med_req = ourCtx.newJsonParser().parseResource(MedicationRequest.class, loadResource("/r4/amz/medication-request-amz.json"));
return med_req;
}
@NotNull
private IValidationSupport getValidationSupport() throws IOException {
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(ourCtx);
npmPackageSupport.loadPackageFromClasspath("classpath:package/hl7.fhir.xver-extensions-0.0.11.tgz");
return new ValidationSupportChain(
new DefaultProfileValidationSupport(ourCtx),
npmPackageSupport
);
}
}

View File

@ -0,0 +1,38 @@
{
"resourceType" : "MedicationRequest",
"meta" : {
"versionId" : "1",
"tag" : [
{
"system" : "https://smarthealthit.org/tags",
"code" : "synthea-8-2017"
}
],
"lastUpdated" : "2021-04-17T02:39:06.885-04:00",
"extension" : [
{
"valueUri" : "https://r2.smarthealthit.org",
"url" : "http://hl7.org/fhir/4.0/StructureDefinition/extension-Meta.source"
}
]
},
"encounter" : {
"reference" : "Encounter/a83e5350-6462-4331-adba-dc72552cecbd"
},
"subject" : {
"reference" : "Patient/ab606cad-b848-4a24-b3b6-61075563fadf"
},
"authoredOn" : "2018-02-19",
"status" : "stopped",
"medicationCodeableConcept" : {
"coding" : [
{
"display" : "Atorvastatin",
"system" : "http://www.nlm.nih.gov/research/umls/rxnorm",
"code" : "259255"
}
],
"text" : "Atorvastatin"
},
"intent" : "order"
}