SMILE-3459 Implemented -l parameter in validator. (#3983)

* SMILE-3459 Implemented -l parameter in validator. Added SnapshotGeneratingValidationSupport to R4 support chain.

* SMILE-3459 Removed failing test that I added.

* Revert "SMILE-3459 Removed failing test that I added."

This reverts commit d11abc7ad5.

* SMILE-3459 Added test cases for loading local R4 profile.

* SMILE-3459 Added when clause to test names.

* SMILE-3459 WIP Logging assert isn't working.

* - changes to test for logging

* - change logging level to error

* - capture all logging events in tests

* SMILE-3459 Ensure validation error is logged when resource doesn't comply with profile.

* SMILE-3459 Added changelog.

* SMILE-3459 Made ValidateCommandTest less brittle.

* SMILE-3459 Changed LocalFileValidationSupport to support all resource types.

* SMILE-3459 Added LocalFileValidationSupportTest

* SMILE-3459 Created ValidationSupportChainCreator.

* SMILE-3459 Added new Msg.code for local profile load failure.

Co-authored-by: kylejule <kyle.jule@smilecdr.com>
Co-authored-by: nathaniel.doef <nathaniel.doef@smilecdr.com>
This commit is contained in:
KGJ-software 2022-09-20 11:36:41 -06:00 committed by GitHub
parent d1f988f98a
commit 4d16a109de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 349 additions and 29 deletions

View File

@ -25,7 +25,7 @@ public final class Msg {
/**
* IMPORTANT: Please update the following comment after you add a new code
* Last used code value: 2140
* Last used code value: 2141
*/
private Msg() {}

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationOptions;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.base.Charsets;
import com.helger.commons.io.file.FileHelper;
@ -40,7 +41,6 @@ import org.fusesource.jansi.Ansi.Color;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.File;
import java.io.FileInputStream;
@ -136,40 +136,27 @@ public class ValidateCommand extends BaseCommand {
FhirContext ctx = getFhirContext();
FhirValidator val = ctx.newValidator();
IBaseResource localProfileResource = null;
ValidationOptions options = new ValidationOptions();
if (theCommandLine.hasOption("l")) {
String localProfile = theCommandLine.getOptionValue("l");
ourLog.info("Loading profile: {}", localProfile);
String input = loadFile(localProfile);
localProfileResource = ca.uhn.fhir.rest.api.EncodingEnum.detectEncodingNoDefault(input).newParser(ctx).parseResource(input);
options.addProfile(localProfile);
}
if (theCommandLine.hasOption("p")) {
switch (ctx.getVersion().getVersion()) {
case DSTU2: {
ValidationSupportChain validationSupport = new ValidationSupportChain(
new DefaultProfileValidationSupport(ctx), new InMemoryTerminologyServerValidationSupport(ctx));
if (theCommandLine.hasOption("r")) {
validationSupport.addValidationSupport((IValidationSupport) new LoadingValidationSupportDstu2());
}
FhirInstanceValidator instanceValidator;
instanceValidator = new FhirInstanceValidator(validationSupport);
ValidationSupportChain validationSupportChain = ValidationSupportChainCreator.getValidationSupportChainDstu2(ctx, theCommandLine);
instanceValidator = new FhirInstanceValidator(validationSupportChain);
val.registerValidatorModule(instanceValidator);
break;
}
case DSTU3:
case R4: {
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
val.registerValidatorModule(instanceValidator);
ValidationSupportChain validationSupport = new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), new InMemoryTerminologyServerValidationSupport(ctx));
if (theCommandLine.hasOption("r")) {
validationSupport.addValidationSupport((IValidationSupport) new LoadingValidationSupportDstu3());
}
instanceValidator.setValidationSupport(validationSupport);
ValidationSupportChain validationSupportChain = ValidationSupportChainCreator.getValidationSupportChainR4(ctx, theCommandLine);
instanceValidator.setValidationSupport(validationSupportChain);
break;
}
default:
@ -182,7 +169,7 @@ public class ValidateCommand extends BaseCommand {
ValidationResult results;
try {
results = val.validateWithResult(contents);
results = val.validateWithResult(contents, options);
} catch (DataFormatException e) {
throw new CommandFailureException(Msg.code(1621) + e.getMessage());
}

View File

@ -0,0 +1,51 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.i18n.Msg;
import org.apache.commons.cli.CommandLine;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.LocalFileValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import java.io.IOException;
public class ValidationSupportChainCreator {
public static ValidationSupportChain getValidationSupportChainR4(FhirContext ctx, CommandLine commandLine) {
ValidationSupportChain chain = new ValidationSupportChain(
new DefaultProfileValidationSupport(ctx),
new InMemoryTerminologyServerValidationSupport(ctx));
if (commandLine.hasOption("l")) {
try {
String localProfile = commandLine.getOptionValue("l");
LocalFileValidationSupport localFileValidationSupport = new LocalFileValidationSupport(ctx);
localFileValidationSupport.loadFile(localProfile);
chain.addValidationSupport(localFileValidationSupport);
chain.addValidationSupport(new SnapshotGeneratingValidationSupport(ctx));
} catch (IOException e) {
throw new RuntimeException(Msg.code(2141) + "Failed to load local profile.", e);
}
}
if (commandLine.hasOption("r")) {
chain.addValidationSupport(new LoadingValidationSupportDstu3());
}
return chain;
}
public static ValidationSupportChain getValidationSupportChainDstu2(FhirContext ctx, CommandLine commandLine) {
ValidationSupportChain chain = new ValidationSupportChain(
new DefaultProfileValidationSupport(ctx), new InMemoryTerminologyServerValidationSupport(ctx));
if (commandLine.hasOption("r")) {
chain.addValidationSupport(new LoadingValidationSupportDstu2());
}
return chain;
}
}

View File

@ -1,22 +1,57 @@
package ca.uhn.fhir.cli;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.read.ListAppender;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.ParseException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class ValidateCommandTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateCommandTest.class);
private final Logger ourLog = (Logger) LoggerFactory.getLogger(ValidateCommand.class);
private ValidateCommand myValidateCommand = new ValidateCommand();
private ListAppender<ILoggingEvent> myListAppender = new ListAppender<>();
@BeforeEach
public void beforeEach() {
ourLog.setLevel(Level.ALL);
myListAppender = new ListAppender<>();
myListAppender.start();
ourLog.addAppender(myListAppender);
}
@AfterEach
public void afterEach() {
myListAppender.stop();
}
@BeforeEach
public void before() {
System.setProperty("test", "true");
}
@Test
public void testValidateLocalProfileDstu3() {
String resourcePath = ValidateCommandTest.class.getResource("/patient-uslab-example1.xml").getFile();
ourLog.info(resourcePath);
App.main(new String[] {
App.main(new String[]{
"validate",
"-v", "dstu3",
"-p",
@ -28,10 +63,23 @@ public class ValidateCommandTest {
String resourcePath = ValidateCommandTest.class.getResource("/patient-uslab-example1.xml").getFile();
ourLog.info(resourcePath);
App.main(new String[] {
App.main(new String[]{
"validate",
"-v", "R4",
"-p",
"-n", resourcePath});
}
@Test
public void validate_withLocalProfileR4_shouldPass_whenResourceCompliesWithProfile() {
String patientJson = ValidateCommandTest.class.getResource("/validate/Patient.json").getFile();
String patientProfile = ValidateCommandTest.class.getResource("/validate/PatientIn-Profile.json").getFile();
App.main(new String[]{
"validate",
"--fhir-version", "r4",
"--profile",
"--file", patientJson,
"-l", patientProfile});
}
}

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.ParseException;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ValidationSupportChainCreatorTest {
private static FhirContext myContextDstu2 = FhirContext.forDstu2();
private static FhirContext myContextR4 = FhirContext.forR4();
private ValidateCommand myValidateCommand = new ValidateCommand();
@Test
public void getR4Chain_shouldProvideExpectedValidationSupport() throws ParseException {
String patientJson = ValidateCommandTest.class.getResource("/validate/Patient-no-identifier.json").getFile();
String patientProfile = ValidateCommandTest.class.getResource("/validate/PatientIn-Profile.json").getFile();
String[] args = new String[]{
"validate",
"--fhir-version", "r4",
"--profile",
"--file", patientJson,
"-l", patientProfile
};
CommandLine commandLine = new DefaultParser().parse(myValidateCommand.getOptions(), args);
ValidationSupportChain chain = ValidationSupportChainCreator.getValidationSupportChainR4(myContextR4, commandLine);
StructureDefinition structureDefinition = (StructureDefinition) chain.fetchStructureDefinition("https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient");
assertEquals(structureDefinition.getName(), "Profile_MII_Patient_PatientIn");
}
}

View File

@ -0,0 +1,9 @@
{
"resourceType": "Patient",
"id": "ExamplePatientPatientFull",
"meta": {
"profile": [
"https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient"
]
}
}

View File

@ -0,0 +1,24 @@
{
"resourceType": "Patient",
"id": "ExamplePatientPatientFull",
"meta": {
"profile": [
"https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}
]
},
"system": "https://www.example.org/fhir/sid/patienten",
"value": "42285243"
}
]
}

View File

@ -0,0 +1,34 @@
{
"resourceType": "StructureDefinition",
"id": "Profile-MII-Patient-PatientIn",
"url": "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient",
"version": "2.0.0",
"name": "Profile_MII_Patient_PatientIn",
"title": "Profile - MI-I - Patient - PatientIn",
"status": "active",
"fhirVersion": "4.0.1",
"kind": "resource",
"abstract": false,
"type": "Patient",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Patient.identifier",
"path": "Patient.identifier",
"slicing": {
"discriminator": [
{
"type": "pattern",
"path": "$this"
}
],
"rules": "open"
},
"min": 1,
"mustSupport": true
}
]
}
}

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 4003
jira: SMILE-3459
title: "Added support for -l parameter for providing a local validation profile in the HAPI FHIR CLI."

View File

@ -0,0 +1,48 @@
package org.hl7.fhir.common.hapi.validation.support;
/*-
* #%L
* HAPI FHIR - Command Line Client - API
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class LocalFileValidationSupport extends PrePopulatedValidationSupport {
public LocalFileValidationSupport(FhirContext ctx) {
super(ctx);
}
@Override
public FhirContext getFhirContext() {
return myCtx;
}
public void loadFile(String theFileName) throws IOException {
String contents = IOUtils.toString(new InputStreamReader(new FileInputStream(theFileName), "UTF-8"));
IBaseResource resource = myCtx.newJsonParser().parseResource(contents);
this.addResource(resource);
}
}

View File

@ -229,7 +229,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setValidatorResourceFetcher(validatorResourceFetcher)
.setValidationPolicyAdvisor(validatorPolicyAdvisor)
.setNoTerminologyChecks(isNoTerminologyChecks())
.setNoExtensibleWarnings(isNoExtensibleWarnings())

View File

@ -0,0 +1,42 @@
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.ValidationSupportContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.EncodingEnum;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.common.hapi.validation.support.LocalFileValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class LocalFileValidationSupportTest {
private FhirContext myFhirCtx = FhirContext.forR4();
private static final Logger ourLog = LoggerFactory.getLogger(LocalFileValidationSupportTest.class);
@Test
public void getStructureDefinition_shouldReturnLocalFile_whenLocalFileProvided() throws IOException {
String patientProfile = LocalFileValidationSupportTest.class.getResource("/PatientIn-Profile.json").getFile();
LocalFileValidationSupport localFileValidationSupport = new LocalFileValidationSupport(myFhirCtx);
localFileValidationSupport.loadFile(patientProfile);
StructureDefinition structureDefinition = (StructureDefinition) localFileValidationSupport.fetchStructureDefinition("https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient");
assertEquals(structureDefinition.getName(), "Profile_MII_Patient_PatientIn");
}
}

View File

@ -0,0 +1,34 @@
{
"resourceType": "StructureDefinition",
"id": "Profile-MII-Patient-PatientIn",
"url": "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient",
"version": "2.0.0",
"name": "Profile_MII_Patient_PatientIn",
"title": "Profile - MI-I - Patient - PatientIn",
"status": "active",
"fhirVersion": "4.0.1",
"kind": "resource",
"abstract": false,
"type": "Patient",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Patient",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Patient.identifier",
"path": "Patient.identifier",
"slicing": {
"discriminator": [
{
"type": "pattern",
"path": "$this"
}
],
"rules": "open"
},
"min": 1,
"mustSupport": true
}
]
}
}