Merge pull request #1311 from hapifhir/gg-202206-validator-watch-mode

Gg 202206 validator watch mode
This commit is contained in:
Grahame Grieve 2023-06-20 09:56:10 +10:00 committed by GitHub
commit 36e94ce4be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 260 additions and 58 deletions

View File

@ -190,7 +190,9 @@ public class TerminologyClientR3 implements ITerminologyClient {
@Override
public ITerminologyClient setClientHeaders(ClientHeaders clientHeaders) {
this.clientHeaders = clientHeaders;
this.client.setClientHeaders(this.clientHeaders.headers());
if (this.clientHeaders != null) {
this.client.setClientHeaders(this.clientHeaders.headers());
}
return this;
}

View File

@ -206,7 +206,9 @@ public class TerminologyClientR4 implements ITerminologyClient {
@Override
public ITerminologyClient setClientHeaders(ClientHeaders clientHeaders) {
this.clientHeaders = clientHeaders;
this.client.setClientHeaders(this.clientHeaders.headers());
if (this.clientHeaders != null) {
this.client.setClientHeaders(this.clientHeaders.headers());
}
return this;
}

View File

@ -103,7 +103,9 @@ public class FhirRequestBuilder {
* @param headers {@link Headers} to add to request.
*/
protected static void addHeaders(Request.Builder request, Headers headers) {
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
if (headers != null) {
headers.forEach(header -> request.addHeader(header.getFirst(), header.getSecond()));
}
}
/**

View File

@ -3174,7 +3174,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (mandatory) {
if (prefixLength == 0)
errors.add("Differential contains path "+path+" which is not found in the in base "+baseName);
errors.add("Differential contains path "+path+" which is not found in the base "+baseName);
else
errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName);
}
@ -3293,7 +3293,9 @@ public class ProfileUtilities extends TranslatingUtilities {
edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
else
Collections.sort(edh.getChildren(), cmp);
cmp.checkForErrors(errors);
if (debug) {
cmp.checkForErrors(errors);
}
for (ElementDefinitionHolder child : edh.getChildren()) {
if (child.getChildren().size() > 0) {

View File

@ -3,19 +3,19 @@ package org.hl7.fhir.r5.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@ -26,7 +26,7 @@ package org.hl7.fhir.r5.context;
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
@ -43,7 +43,7 @@ import org.hl7.fhir.utilities.Utilities;
public class TextClientLogger extends BaseLogger implements ToolingClientLogger {
private PrintStream file;
public TextClientLogger(String log) {
if (log != null) {
try {
@ -60,8 +60,10 @@ public class TextClientLogger extends BaseLogger implements ToolingClientLogger
String id = nextId();
file.println("\r\n--- "+id+" -----------------\r\nRequest: \r\n");
file.println(method+" "+url+" HTTP/1.0");
for (String s : headers)
file.println(s);
if (headers != null) {
for (String s : headers)
file.println(s);
}
if (body != null) {
file.println("");
try {

View File

@ -57,7 +57,7 @@ public class FmlParser extends ParserBase {
}
public Element parse(String text) throws FHIRException {
FHIRLexer lexer = new FHIRLexer(text, "source", true);
FHIRLexer lexer = new FHIRLexer(text, "source", true, true);
if (lexer.done())
throw lexer.error("Map Input cannot be empty");
Element result = Manager.build(context, context.fetchTypeDefinition("StructureMap"));

View File

@ -399,7 +399,7 @@ public class AdditionalBindingsRenderer {
return; // what should happen?
}
BindingResolution br = pkp.resolveBinding(profile, b.getValueSet(), corePath);
XhtmlNode a = children.ah(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : null);
XhtmlNode a = children.ahOrCode(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : null);
if (b.hasDocumentation()) {
a.attribute("title", b.getDocumentation());
}

View File

@ -979,9 +979,9 @@ public class ValueSetValidator {
for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) {
Boolean ok = inComponent(path, vsi, i, system, version, code, valueset.getCompose().getInclude().size() == 1, info);
i++;
if (ok == null && result == false) {
if (ok == null && result != null && result == false) {
result = null;
} else if (ok) {
} else if (ok != null && ok) {
result = true;
break;
}
@ -990,7 +990,7 @@ public class ValueSetValidator {
for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) {
Boolean nok = inComponent(path, vsi, i, system, version, code, valueset.getCompose().getInclude().size() == 1, info);
i++;
if (nok == null && result == false) {
if (nok == null && result != null && result == false) {
result = null;
} else if (nok != null && nok) {
result = false;
@ -1200,6 +1200,7 @@ public class ValueSetValidator {
}
ValueSet vs = context.fetchResource(ValueSet.class, url, valueset);
ValueSetValidator vsc = new ValueSetValidator(options, vs, context, localContext, expansionProfile, txCaps);
vsc.setThrowToServer(throwToServer);
inner.put(url, vsc);
return vsc;
}

View File

@ -88,6 +88,7 @@ public class FHIRLexer {
private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
private SourceLocation commentLocation;
private boolean metadataFormat;
private boolean allowDoubleQuotes;
public FHIRLexer(String source, String name) throws FHIRLexerException {
this.source = source == null ? "" : source;
@ -101,10 +102,18 @@ public class FHIRLexer {
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, String name, boolean metadataFormat) throws FHIRLexerException {
public FHIRLexer(String source, int i, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source;
this.cursor = i;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, String name, boolean metadataFormat, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source == null ? "" : source;
this.name = name == null ? "??" : name;
this.metadataFormat = metadataFormat;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
@ -235,7 +244,7 @@ public class FHIRLexer {
if (ch == '}')
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '"') {
} else if (ch == '"' && allowDoubleQuotes) {
cursor++;
boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) {
@ -588,5 +597,7 @@ public class FHIRLexer {
return null;
}
}
public boolean isAllowDoubleQuotes() {
return allowDoubleQuotes;
}
}

View File

@ -280,6 +280,7 @@ public class FHIRPathEngine {
private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
private boolean doNotEnforceAsSingletonRule;
private boolean doNotEnforceAsCaseSensitive;
private boolean allowDoubleQuotes;
// if the fhir path expressions are allowed to use constants beyond those defined in the specification
// the application can implement them by providing a constant resolver
@ -511,7 +512,7 @@ public class FHIRPathEngine {
}
public ExpressionNode parse(String path, String name) throws FHIRLexerException {
FHIRLexer lexer = new FHIRLexer(path, name);
FHIRLexer lexer = new FHIRLexer(path, name, false, allowDoubleQuotes);
if (lexer.done()) {
throw lexer.error("Path cannot be empty");
}
@ -548,7 +549,7 @@ public class FHIRPathEngine {
* @throws Exception
*/
public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException {
FHIRLexer lexer = new FHIRLexer(path, i);
FHIRLexer lexer = new FHIRLexer(path, i, allowDoubleQuotes);
if (lexer.done()) {
throw lexer.error("Path cannot be empty");
}
@ -4150,7 +4151,6 @@ public class FHIRPathEngine {
String param = nl.get(0).primitiveValue();
List<Base> result = new ArrayList<Base>();
if (focus.size() == 1) {
String cnt = focus.get(0).primitiveValue();
if ("hex".equals(param)) {
@ -4163,7 +4163,6 @@ public class FHIRPathEngine {
result.add(new StringType(new String(enc.decode(cnt))));
}
}
return result;
}
@ -5816,7 +5815,11 @@ public class FHIRPathEngine {
String tail = "";
StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
if (sd == null) {
throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, url, "getChildTypesByName");
if (url.startsWith(TypeDetails.FP_NS)) {
return;
} else {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName");
}
}
List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
ElementDefinitionMatch m = null;
@ -5826,14 +5829,14 @@ public class FHIRPathEngine {
if (m.fixedType != null) {
StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null), sd);
if (dt == null) {
throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName");
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName");
}
sdl.add(dt);
} else
for (TypeRefComponent t : m.definition.getType()) {
StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), null));
if (dt == null) {
throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName");
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName");
}
addTypeAndDescendents(sdl, dt, cu.allStructures());
// also add any descendant types
@ -6392,4 +6395,10 @@ public class FHIRPathEngine {
return profileUtilities;
}
public boolean isAllowDoubleQuotes() {
return allowDoubleQuotes;
}
public void setAllowDoubleQuotes(boolean allowDoubleQuotes) {
this.allowDoubleQuotes = allowDoubleQuotes;
}
}

View File

@ -176,7 +176,7 @@ public class LiquidEngine implements IEvaluationContext {
@Override
public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException {
if (compiled.size() == 0) {
FHIRLexer lexer = new FHIRLexer(statement, "liquid statement");
FHIRLexer lexer = new FHIRLexer(statement, "liquid statement", false, true);
lexer.setLiquidMode(true);
compiled.add(new LiquidExpressionNode(null, engine.parse(lexer)));
while (!lexer.done()) {

View File

@ -626,7 +626,7 @@ public class StructureMapUtilities {
}
public StructureMap parse(String text, String srcName) throws FHIRException {
FHIRLexer lexer = new FHIRLexer(Utilities.stripBOM(text), srcName, true);
FHIRLexer lexer = new FHIRLexer(Utilities.stripBOM(text), srcName, true, true);
if (lexer.done())
throw lexer.error("Map Input cannot be empty");
StructureMap result = new StructureMap();

View File

@ -81,6 +81,7 @@ public class BaseWorkerContextTests {
public String getSpecUrl() {
return null;
}
};
baseWorkerContext.expParameters = new Parameters();
return baseWorkerContext;

View File

@ -8,7 +8,7 @@ class FHIRLexerTest {
@Test
@DisplayName("Test that a 'null' current value returns 'false' when FHIRLexer.isConstant() is called, and not NPE.")
void getCurrent() {
FHIRLexer lexer = new FHIRLexer(null, null);
FHIRLexer lexer = new FHIRLexer(null, null, false, true);
String lexerCurrent = lexer.getCurrent();
Assertions.assertNull(lexerCurrent);
Assertions.assertFalse(lexer.isConstant());

View File

@ -163,8 +163,10 @@ public class SimpleHTTPClient {
}
private void setHeaders(HttpURLConnection c) {
for (Header h : headers) {
c.setRequestProperty(h.getName(), h.getValue());
if (headers != null) {
for (Header h : headers) {
c.setRequestProperty(h.getName(), h.getValue());
}
}
c.setConnectTimeout(15000);
c.setReadTimeout(15000);

View File

@ -171,6 +171,7 @@ public class I18nConstants {
public static final String FHIRPATH_NOT_IMPLEMENTED = "FHIRPATH_NOT_IMPLEMENTED";
public static final String FHIRPATH_NO_COLLECTION = "FHIRPATH_NO_COLLECTION";
public static final String FHIRPATH_NO_TYPE = "FHIRPATH_NO_TYPE";
public static final String FHIRPATH_UNKNOWN_TYPE = "FHIRPATH_UNKNOWN_TYPE";
public static final String FHIRPATH_NUMERICAL_ONLY = "FHIRPATH_NUMERICAL_ONLY";
public static final String FHIRPATH_OP_INCOMPATIBLE = "FHIRPATH_OP_INCOMPATIBLE";
public static final String FHIRPATH_ORDERED_ONLY = "FHIRPATH_ORDERED_ONLY";
@ -909,6 +910,10 @@ public class I18nConstants {
public static final String QUESTIONNAIRE_Q_ITEM_DERIVED_ANSWER_OPTIONS_NEW = "QUESTIONNAIRE_Q_ITEM_DERIVED_ANSWER_OPTIONS_NEW";
public static final String PRIMITIVE_MUSTHAVEVALUE_MESSAGE = "PRIMITIVE_MUSTHAVEVALUE_MESSAGE";
public static final String PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE = "PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE";
public static final String ED_INVARIANT_NO_KEY = "ED_INVARIANT_NO_KEY";
public static final String ED_INVARIANT_NO_EXPRESSION = "ED_INVARIANT_NO_EXPRESSION";
public static final String ED_INVARIANT_EXPRESSION_CONFLICT = "ED_INVARIANT_EXPRESSION_CONFLICT";
public static final String ED_INVARIANT_EXPRESSION_ERROR = "ED_INVARIANT_EXPRESSION_ERROR";
}

View File

@ -121,6 +121,26 @@ public abstract class XhtmlFluent {
return x;
}
/**
* make it a code if it's not a link
* @param href
* @param title
* @return
*/
public XhtmlNode ahOrCode(String href, String title) {
if (href != null) {
return ah(href, title);
} else if (title != null) {
return code().setAttribute("title", title);
} else {
return code();
}
}
public XhtmlNode ahOrCode(String href) {
return ahOrCode(href, null);
}
public XhtmlNode img(String src, String alt) {
return addTag("img").attribute("src", src).attribute("alt", alt);
}

View File

@ -610,7 +610,8 @@ FHIRPATH_NO_COLLECTION = Error evaluating FHIRPath expression: The function {0}
FHIRPATH_NOT_IMPLEMENTED = Error evaluating FHIRPath expression: The function {0} is not implemented
FHIRPATH_PARAM_WRONG = Error evaluating FHIRPath expression: The expression type {0} is not supported for parameter {1} on function {2}
FHIRPATH_CHECK_FAILED = Error evaluating FHIRPath expression: The check {0} failed
FHIRPATH_NO_TYPE = Error evaluating FHIRPath expression: The type ''{0}'' is unknown or not supported at {1}
FHIRPATH_NO_TYPE = Error evaluating FHIRPath expression: No type provided at {1}
FHIRPATH_UNKNOWN_TYPE = Error evaluating FHIRPath expression: The type ''{0}'' is unknown or not supported at {1}
FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED = Error in discriminator at {0}: found a sliced element while resolving the fixed value for one of the slices
FHIRPATH_DISCRIMINATOR_THIS_CANNOT_FIND = Problem with use of resolve() - profile {0} on {1} could not be resolved
FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE = Invalid use of resolve() in discriminator - no type on element {0}
@ -963,3 +964,8 @@ QUESTIONNAIRE_Q_ITEM_DERIVED_ANSWER_OPTIONS_NEW = The item with linkId ''{1}'' f
PRIMITIVE_MUSTHAVEVALUE_MESSAGE = The element definition ``{0}`` in the profile ''{1}'' requires that a value be present in this element
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_one = The element definition ``{0}`` in the profile ''{1}'' requires that if a value is not present, the extension ''{2}'' must be present
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_other = The element definition ``{0}`` in the profile ''{1}'' requires that if a value is not present, one of the extensions ''{2}'' must be present
ED_INVARIANT_NO_KEY = The invariant has no key, so the content cannot be validated
ED_INVARIANT_NO_EXPRESSION = The invariant ''{0}'' has no computable expression, so validators will not be able to check it
ED_INVARIANT_EXPRESSION_CONFLICT = The invariant ''{0}'' has an expression ''{1}'', which differs from the earlier expression provided of ''{2}'' (invariants are allowed to repeat, but cannot differ)
ED_INVARIANT_EXPRESSION_ERROR = Error in invariant ''{0}'' with expression ''{1}'': {2}

View File

@ -211,6 +211,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
@Getter @Setter private boolean showMessagesFromReferences;
@Getter @Setter private boolean doImplicitFHIRPathStringConversion;
@Getter @Setter private HtmlInMarkdownCheck htmlInMarkdownCheck;
@Getter @Setter private boolean allowDoubleQuotesInFHIRPath;
@Getter @Setter private Locale locale;
@Getter @Setter private List<ImplementationGuide> igs = new ArrayList<>();
@Getter @Setter private List<String> extensionDomains = new ArrayList<>();
@ -262,6 +263,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
showMessagesFromReferences = other.showMessagesFromReferences;
doImplicitFHIRPathStringConversion = other.doImplicitFHIRPathStringConversion;
htmlInMarkdownCheck = other.htmlInMarkdownCheck;
allowDoubleQuotesInFHIRPath = other.allowDoubleQuotesInFHIRPath;
locale = other.locale;
igs.addAll(other.igs);
extensionDomains.addAll(other.extensionDomains);
@ -471,6 +473,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
context.loadFromPackage(npmX, null);
this.fhirPathEngine = new FHIRPathEngine(context);
this.fhirPathEngine.setAllowDoubleQuotes(false);
}
private String getVersionFromPack(Map<String, byte[]> source) {
@ -839,6 +842,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
validator.setQuestionnaireMode(questionnaireMode);
validator.setLevel(level);
validator.setHtmlInMarkdownCheck(htmlInMarkdownCheck);
validator.setAllowDoubleQuotesInFHIRPath(allowDoubleQuotesInFHIRPath);
validator.setNoUnicodeBiDiControlChars(noUnicodeBiDiControlChars);
validator.setDoImplicitFHIRPathStringConversion(doImplicitFHIRPathStringConversion);
if (format == FhirFormat.SHC) {

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.validation.cli.services.ValidatorWatchMode;
import org.hl7.fhir.validation.cli.utils.EngineMode;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.cli.utils.ValidationLevel;
@ -53,7 +54,8 @@ public class CliContext {
private boolean doImplicitFHIRPathStringConversion = false;
@JsonProperty("htmlInMarkdownCheck")
private HtmlInMarkdownCheck htmlInMarkdownCheck = HtmlInMarkdownCheck.WARNING;
@JsonProperty("allowDoubleQuotesInFHIRPath")
private boolean allowDoubleQuotesInFHIRPath = false;
@JsonProperty("langTransform")
private String langTransform = null;
@JsonProperty("map")
@ -139,6 +141,8 @@ public class CliContext {
@JsonProperty("fhirSettingsFile")
private String fhirSettingsFile;
@JsonProperty("watchMode")
private ValidatorWatchMode watchMode = ValidatorWatchMode.NONE;
@JsonProperty("map")
public String getMap() {
@ -294,6 +298,16 @@ public class CliContext {
this.htmlInMarkdownCheck = htmlInMarkdownCheck;
}
@JsonProperty("allowDoubleQuotesInFHIRPath")
public boolean isAllowDoubleQuotesInFHIRPath() {
return allowDoubleQuotesInFHIRPath;
}
@JsonProperty("allowDoubleQuotesInFHIRPath")
public void setAllowDoubleQuotesInFHIRPath(boolean allowDoubleQuotesInFHIRPath) {
this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath;
}
@JsonProperty("locale")
public String getLanguageCode() {
return locale;
@ -705,8 +719,10 @@ public class CliContext {
noInvariants == that.noInvariants &&
displayWarnings == that.displayWarnings &&
wantInvariantsInMessages == that.wantInvariantsInMessages &&
allowDoubleQuotesInFHIRPath == that.allowDoubleQuotesInFHIRPath &&
Objects.equals(extensions, that.extensions) &&
Objects.equals(map, that.map) &&
Objects.equals(htmlInMarkdownCheck, that.htmlInMarkdownCheck) &&
Objects.equals(output, that.output) &&
Objects.equals(outputSuffix, that.outputSuffix) &&
Objects.equals(htmlOutput, that.htmlOutput) &&
@ -727,21 +743,23 @@ public class CliContext {
Objects.equals(profiles, that.profiles) &&
Objects.equals(sources, that.sources) &&
Objects.equals(crumbTrails, that.crumbTrails) &&
Objects.equals(forPublication, that.forPublication) &&
Objects.equals(forPublication, that.forPublication)&&
Objects.equals(allowExampleUrls, that.allowExampleUrls) &&
Objects.equals(showTimes, that.showTimes) &&
mode == that.mode &&
Objects.equals(locale, that.locale) &&
Objects.equals(outputStyle, that.outputStyle) &&
Objects.equals(jurisdiction, that.jurisdiction) &&
Objects.equals(locations, that.locations);
Objects.equals(locations, that.locations) &&
Objects.equals(watchMode, that.watchMode) ;
}
@Override
public int hashCode() {
return Objects.hash(doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars);
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, watchMode,
htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath);
}
@Override
@ -792,6 +810,9 @@ public class CliContext {
", locale='" + locale + '\'' +
", locations=" + locations +
", bundleValidationRules=" + bundleValidationRules +
", htmlInMarkdownCheck=" + htmlInMarkdownCheck +
", allowDoubleQuotesInFHIRPath=" + allowDoubleQuotesInFHIRPath +
", watchMode=" + watchMode +
'}';
}
@ -805,4 +826,17 @@ public class CliContext {
public String getFhirSettingsFile() {
return fhirSettingsFile;
}
@JsonProperty("watchMode")
public ValidatorWatchMode getWatchMode() {
return watchMode;
}
@JsonProperty("watchMode")
public CliContext setWatchMode(ValidatorWatchMode watchMode) {
this.watchMode = watchMode;
return this;
}
}

View File

@ -143,11 +143,10 @@ public class ValidationService {
return versions;
}
public void validateSources(CliContext cliContext, ValidationEngine validator) throws Exception {
public void validateSources(CliContext cliContext, ValidationEngine validator, ValidatorWatchMode watch) throws Exception {
if (cliContext.getProfiles().size() > 0) {
System.out.println(" Profiles: " + cliContext.getProfiles());
}
ValidatorWatchMode watch = ValidatorWatchMode.NONE;
IgLoader igLoader = new IgLoader(validator.getPcm(), validator.getContext(), validator.getVersion());
List<ValidationRecord> records = new ArrayList<>();
@ -467,6 +466,7 @@ public class ValidationService {
validationEngine.setShowMessagesFromReferences(cliContext.isShowMessagesFromReferences());
validationEngine.setDoImplicitFHIRPathStringConversion(cliContext.isDoImplicitFHIRPathStringConversion());
validationEngine.setHtmlInMarkdownCheck(cliContext.getHtmlInMarkdownCheck());
validationEngine.setAllowDoubleQuotesInFHIRPath(cliContext.isAllowDoubleQuotesInFHIRPath());
validationEngine.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
validationEngine.setNoUnicodeBiDiControlChars(cliContext.isNoUnicodeBiDiControlChars());
validationEngine.setNoInvariantChecks(cliContext.isNoInvariants());
@ -690,7 +690,7 @@ public class ValidationService {
if (!sd.hasSnapshot()) {
StructureDefinition base = validator.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
cs++;
new ProfileUtilities(validator.getContext(), null, null).setAutoFixSliceNames(true).generateSnapshot(base, sd, sd.getUrl(), null, sd.getName());
new ProfileUtilities(validator.getContext(), new ArrayList<ValidationMessage>(), null).setAutoFixSliceNames(true).generateSnapshot(base, sd, sd.getUrl(), null, sd.getName());
validator.handleOutput(sd, filename, validator.getVersion());
}
}

View File

@ -7,6 +7,7 @@ import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.services.ValidationService;
import org.hl7.fhir.validation.cli.services.ValidatorWatchMode;
import org.hl7.fhir.validation.cli.utils.Display;
import java.io.PrintStream;
@ -54,7 +55,7 @@ public class ValidateTask extends ValidationEngineTask {
}
System.out.println("Validating");
validationService.validateSources(cliContext, validationEngine);
validationService.validateSources(cliContext, validationEngine, cliContext.getWatchMode());
}
}

View File

@ -10,6 +10,7 @@ import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.services.ValidatorWatchMode;
public class Params {
@ -83,6 +84,7 @@ public class Params {
public static final String HTML_IN_MARKDOWN = "-html-in-markdown";
public static final String SRC_LANG = "-src-lang";
public static final String TGT_LANG = "-tgt-lang";
public static final String ALLOW_DOUBLE_QUOTES = "-allow-double-quotes-in-fhirpath";
public static final String RUN_TESTS = "-run-tests";
@ -96,6 +98,7 @@ public class Params {
public static final String INPUT = "-input";
public static final String FILTER = "-filter";
private static final String FHIR_SETTINGS_PARAM = "-fhir-settings";
private static final String WATCH_MODE_PARAM = "-watch-mode";
/**
* Checks the list of passed in params to see if it contains the passed in param.
@ -240,6 +243,8 @@ public class Params {
cliContext.setNoInternalCaching(true);
} else if (args[i].equals(NO_EXTENSIBLE_BINDING_WARNINGS)) {
cliContext.setNoExtensibleBindingMessages(true);
} else if (args[i].equals(ALLOW_DOUBLE_QUOTES)) {
cliContext.setAllowDoubleQuotesInFHIRPath(true);
} else if (args[i].equals(NO_UNICODE_BIDI_CONTROL_CHARS)) {
cliContext.setNoUnicodeBiDiControlChars(true);
} else if (args[i].equals(NO_INVARIANTS)) {
@ -381,6 +386,12 @@ public class Params {
} else {
throw new Exception("Can only nominate a single -map parameter");
}
} else if (args[i].equals(WATCH_MODE_PARAM)) {
if (i + 1 == args.length) {
throw new Error("Specified -watch-mode without indicating mode value");
} else {
cliContext.setWatchMode(readWatchMode(args[++i]));
}
} else if (args[i].startsWith(X)) {
i++;
} else if (args[i].equals(CONVERT)) {
@ -402,6 +413,21 @@ public class Params {
return cliContext;
}
private static ValidatorWatchMode readWatchMode(String s) {
if (s == null) {
return ValidatorWatchMode.NONE;
}
switch (s.toLowerCase()) {
case "all" : return ValidatorWatchMode.ALL;
case "none" : return ValidatorWatchMode.NONE;
case "single" : return ValidatorWatchMode.SINGLE;
case "a" : return ValidatorWatchMode.ALL;
case "n" : return ValidatorWatchMode.NONE;
case "s" : return ValidatorWatchMode.SINGLE;
}
throw new Error("The watch mode ''"+s+"'' is not valid");
}
private static String processJurisdiction(String s) {
if (s.startsWith("urn:iso:std:iso:3166#") || s.startsWith("urn:iso:std:iso:3166:-2#") || s.startsWith("http://unstats.un.org/unsd/methods/m49/m49.htm#")) {
return s;

View File

@ -466,7 +466,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean noUnicodeBiDiControlChars;
private HtmlInMarkdownCheck htmlInMarkdownCheck;
private boolean allowComments;
private boolean displayWarnings;
private boolean allowDoubleQuotesInFHIRPath;
private List<ImplementationGuide> igs = new ArrayList<>();
private List<String> extensionDomains = new ArrayList<String>();
@ -518,6 +518,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
fpe.setLegacyMode(true);
source = Source.InstanceValidator;
fpe.setDoNotEnforceAsSingletonRule(!VersionUtilities.isR5VerOrLater(theContext.getVersion()));
fpe.setAllowDoubleQuotes(allowDoubleQuotesInFHIRPath);
}
@Override
@ -2036,12 +2037,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
} else if (ctxt.getType() == ExtensionContextType.EXTENSION) {
contexts.append("x:" + ctxt.getExpression());
NodeStack estack = stack.getParent();
if (estack != null && estack.getElement().fhirType().equals("Extension")) {
String ext = estack.getElement().getNamedChildValue("url");
if (ctxt.getExpression().equals(ext)) {
ok = true;
String ext = null;
if (stack.getElement().getName().startsWith("value")) {
NodeStack estack = stack.getParent();
if (estack != null && estack.getElement().fhirType().equals("Extension")) {
ext = estack.getElement().getNamedChildValue("url");
}
} else {
ext = stack.getElement().getNamedChildValue("url");
}
if (ctxt.getExpression().equals(ext)) {
ok = true;
} else if (ext != null) {
plist.add(ext);
}
} else if (ctxt.getType() == ExtensionContextType.FHIRPATH) {
contexts.append("p:" + ctxt.getExpression());
@ -6490,6 +6498,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
public boolean isAllowDoubleQuotesInFHIRPath() {
return allowDoubleQuotesInFHIRPath;
}
public void setAllowDoubleQuotesInFHIRPath(boolean allowDoubleQuotesInFHIRPath) {
this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath;
}
public static void setParents(Element element) {
if (element != null && !element.hasParentForValidator()) {
element.setParentForValidator(null);

View File

@ -5,7 +5,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
@ -129,10 +131,10 @@ public class StructureDefinitionValidator extends BaseValidator {
boolean logical = "logical".equals(src.getNamedChildValue("kind"));
boolean constraint = "constraint".equals(src.getNamedChildValue("derivation"));
for (Element differential : differentials) {
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint) && ok;
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint, src.getNamedChildValue("type"), src.getNamedChildValue("url")) && ok;
}
for (Element snapshotE : snapshots) {
ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint) && ok;
ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint, src.getNamedChildValue("type"), src.getNamedChildValue("url")) && ok;
}
// obligation profile support
@ -330,18 +332,19 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) {
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl) {
Map<String, String> invariantMap = new HashMap<>();
boolean ok = true;
List<Element> elements = elementList.getChildrenByName("element");
int cc = 0;
for (Element element : elements) {
ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint) && ok;
ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint, invariantMap, rootPath, profileUrl) && ok;
cc++;
}
return ok;
}
private boolean validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) {
private boolean validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map<String, String> invariantMap, String rootPath, String profileUrl) {
boolean ok = true;
boolean typeMustSupport = false;
String path = element.getNamedChildValue("path");
@ -466,9 +469,40 @@ public class StructureDefinitionValidator extends BaseValidator {
}
// if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint
}
List<Element> constraints = element.getChildrenByName("constraint");
int cc = 0;
for (Element invariant : constraints) {
ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, element.getNamedChildValue("path"), rootPath, profileUrl) && ok;
cc++;
}
return ok;
}
private boolean validateElementDefinitionInvariant(List<ValidationMessage> errors, Element invariant, NodeStack stack, Map<String, String> invariantMap, String path, String rootPath, String profileUrl) {
boolean ok = true;
String key = invariant.getNamedChildValue("key");
String expression = invariant.getNamedChildValue("expression");
String source = invariant.getNamedChildValue("source");
if (warning(errors, "2023-06-19", IssueType.INFORMATIONAL, stack, !Utilities.noString(key), I18nConstants.ED_INVARIANT_NO_KEY)) {
if (hint(errors, "2023-06-19", IssueType.INFORMATIONAL, stack, !Utilities.noString(expression), I18nConstants.ED_INVARIANT_NO_EXPRESSION, key)) {
if (invariantMap.containsKey(key)) {
// it's legal - and common - for a list of elemnts to contain the same invariant more than once, but it's not valid if it's not always the same
ok = rule(errors, "2023-06-19", IssueType.INVALID, stack, expression.equals(invariantMap.get(key)), I18nConstants.ED_INVARIANT_EXPRESSION_CONFLICT, key, expression, invariantMap.get(key));
} else {
invariantMap.put(key, expression);
}
if (Utilities.noString(source) || (source.equals(profileUrl))) { // no need to revalidate FHIRPath from elsewhere
try {
fpe.check(invariant, rootPath, path, fpe.parse(expression));
} catch (Exception e) {
ok = rule(errors, "2023-06-19", IssueType.INVALID, stack, false, I18nConstants.ED_INVARIANT_EXPRESSION_ERROR, key, expression, e.getMessage()) && ok;
}
}
}
}
return ok;
}
private boolean meaningWhenMissingAllowed(Element element) {
// allowed to use meaningWhenMissing on the root of an element to say what it means when the extension
// is not present.

View File

@ -51,11 +51,13 @@ public class ProfileValidator extends BaseValidator {
private boolean checkAggregation = false;
private boolean checkMustSupport = false;
private boolean allowDoubleQuotesInFHIRPath = false;
private FHIRPathEngine fpe;
public ProfileValidator(IWorkerContext context, XVerExtensionManager xverManager) {
super(context, xverManager);
fpe = new FHIRPathEngine(context);
fpe.setAllowDoubleQuotes(allowDoubleQuotesInFHIRPath);
}
public boolean isCheckAggregation() {
@ -74,6 +76,14 @@ public class ProfileValidator extends BaseValidator {
this.checkMustSupport = checkMustSupport;
}
public boolean isAllowDoubleQuotesInFHIRPath() {
return allowDoubleQuotesInFHIRPath;
}
public void setAllowDoubleQuotesInFHIRPath(boolean allowDoubleQuotesInFHIRPath) {
this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath;
}
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
String rn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
return super.ruleHtml(errors, NO_RULE_DATE, type, path, b, msg, "<a href=\""+(rn.toLowerCase())+".html\">"+rn+"</a>: "+Utilities.escapeXml(msg));

View File

@ -5,8 +5,8 @@ The validation tool compares a resource against the base definitions and any
profiles declared in the resource (Resource.meta.profile) or specified on the
command line
The FHIR validation tool validates a FHIR resource or bundle. Schema and
schematron checking is performed, then some additional checks are performed.
The FHIR validation tool validates a FHIR resource or bundle. Syntax and content is checked
against the specification and other profiles as specified.
* XML & Json (FHIR versions {{XML_AND_JSON_FHIR_VERSIONS}})
* Turtle (FHIR versions {{TURTLE_FHIR_VERSIONS}})
@ -72,6 +72,16 @@ The following parameters are supported:
Default: results are sent to the std out.
-outputSuffix [string]: used in -convert and -snapshot to deal with
one or more result files (where -output can only have one)
-watch-mode [mode]:
Specify that the validator remain running and re-validate when any
of the validated files changes. The validator has to be terminated with
ctrl-c etc in this mode.
This parameter can have one of the following values:
* none: the default - don't wait, just stop when finished
* single: when any of the validated files changes, re-validate it
* all: when any of the validated files changes, re-validate all of them
All is useful when the content includes internal dependencies e.g.
a profile and it's value sets.
-debug
Produce additional information about the loading/validation process
-recurse

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.validation;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.services.ValidationService;
import org.hl7.fhir.validation.cli.services.ValidatorWatchMode;
import org.hl7.fhir.validation.cli.tasks.*;
import org.hl7.fhir.validation.cli.utils.Params;
@ -245,9 +246,10 @@ public class ValidatorCliTests {
final String[] args = new String[]{"dummyFile.json"};
CliContext cliContext = Params.loadCliContext(args);
ValidatorCli cli = mockValidatorCliWithService(cliContext);
ValidatorWatchMode watchMode = ValidatorWatchMode.NONE;
cli.readParamsAndExecuteTask(cliContext, args);
Mockito.verify(validationService).determineVersion(same(cliContext));
Mockito.verify(validationService).validateSources(same(cliContext), same(validationEngine));
Mockito.verify(validationService).validateSources(same(cliContext), same(validationEngine), same(watchMode));
}
@Test