From 38b5295f8d5bdf50da40d10071cd0a3e0898bd4a Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Jun 2023 22:39:42 +1000 Subject: [PATCH 01/10] fix error logging headers --- .../txClient/TerminologyClientR3.java | 4 +++- .../txClient/TerminologyClientR4.java | 4 +++- .../client/network/FhirRequestBuilder.java | 4 +++- .../hl7/fhir/r5/context/TextClientLogger.java | 22 ++++++++++--------- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java index 5f15fa54f..b7872a919 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java @@ -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; } diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java index 957d35b11..007a6b66c 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java @@ -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; } diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java index e8a59f5f5..b2231579f 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/network/FhirRequestBuilder.java @@ -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())); + } } /** diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TextClientLogger.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TextClientLogger.java index c3450f72b..78333302a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TextClientLogger.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TextClientLogger.java @@ -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 { From d1a785e5ad5a3218bec5d3ab96762f7151242d10 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Jun 2023 22:40:08 +1000 Subject: [PATCH 02/10] only record sorting errors if debugging snapshot generation --- .../hl7/fhir/r5/conformance/profile/ProfileUtilities.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 4758526db..39f2f408f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -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) { From 19d3952b11392173c0153cc09985984b0e07c0cc Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Jun 2023 22:42:02 +1000 Subject: [PATCH 03/10] Validate FHIRPath constraints in IGs and profiles --- .../hl7/fhir/r5/elementmodel/FmlParser.java | 2 +- .../java/org/hl7/fhir/r5/utils/FHIRLexer.java | 17 +++++-- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 21 ++++++--- .../org/hl7/fhir/r5/utils/LiquidEngine.java | 2 +- .../structuremap/StructureMapUtilities.java | 2 +- .../r5/context/BaseWorkerContextTests.java | 6 +++ .../org/hl7/fhir/r5/utils/FHIRLexerTest.java | 2 +- .../hl7/fhir/utilities/SimpleHTTPClient.java | 6 ++- .../instance/InstanceValidator.java | 22 +++++++--- .../type/StructureDefinitionValidator.java | 44 ++++++++++++++++--- .../validation/profile/ProfileValidator.java | 10 +++++ 11 files changed, 108 insertions(+), 26 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java index f4629aeeb..0664857d8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java @@ -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")); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java index 1b521b471..af0183b4e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java @@ -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; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 2d805a672..cf570ac85 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -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"); } @@ -5816,7 +5817,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 sdl = new ArrayList(); ElementDefinitionMatch m = null; @@ -5826,14 +5831,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 +6397,10 @@ public class FHIRPathEngine { return profileUtilities; } + public boolean isAllowDoubleQuotes() { + return allowDoubleQuotes; + } + public void setAllowDoubleQuotes(boolean allowDoubleQuotes) { + this.allowDoubleQuotes = allowDoubleQuotes; + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java index c9f4772dc..e4bbf2103 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/LiquidEngine.java @@ -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()) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java index 5e91f5438..9e5420cb8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -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(); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java index e32537104..f49ae01e1 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java @@ -3,6 +3,7 @@ package org.hl7.fhir.r5.context; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.model.PackageInformation; import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.utilities.npm.BasePackageCacheManager; @@ -75,6 +76,11 @@ public class BaseWorkerContextTests { public String getSpecUrl() { return null; } + + @Override + public T fetchResourceRaw(Class class_, String uri) { + return null; + } }; baseWorkerContext.expParameters = new Parameters(); return baseWorkerContext; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/FHIRLexerTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/FHIRLexerTest.java index 73637fcde..79b7ad31f 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/FHIRLexerTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/FHIRLexerTest.java @@ -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()); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java index f97cef2f9..7f85adbcd 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java @@ -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); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index c4fb250b1..4e9b9139a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -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 igs = new ArrayList<>(); private List extensionDomains = new ArrayList(); @@ -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,11 @@ 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 = stack.getElement().getNamedChildValue("url"); + if (ctxt.getExpression().equals(ext)) { + ok = true; + } else { + plist.add(ext); } } else if (ctxt.getType() == ExtensionContextType.FHIRPATH) { contexts.append("p:" + ctxt.getExpression()); @@ -6490,6 +6490,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); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 643771e34..2e8e5924e 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -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 errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) { + private boolean validateElementList(List errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl) { + Map invariantMap = new HashMap<>(); boolean ok = true; List 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 errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) { + private boolean validateElementDefinition(List errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map 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 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 errors, Element invariant, NodeStack stack, Map 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. diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java index 7066d12d9..2b3ebec25 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java @@ -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 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, ""+rn+": "+Utilities.escapeXml(msg)); From ac68a9942934efd05d9a6829177ea4223cb02f86 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Jun 2023 22:43:24 +1000 Subject: [PATCH 04/10] More validate invariants --- .../java/org/hl7/fhir/utilities/i18n/I18nConstants.java | 5 +++++ .../src/main/resources/Messages.properties | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 657bd42a8..128f5d60e 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -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"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index f55fc3577..4885f8fe5 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -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} + From 394077c8ece8e52b2a32814981741baeadbd2825 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Jun 2023 22:43:43 +1000 Subject: [PATCH 05/10] fix NPE doing valueset validation --- .../r5/terminologies/validation/ValueSetValidator.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java index 1079bea84..ba1ebff9e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java @@ -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; } From 2b8e61e2cbfb9272ff2d345912708fc5c3192e3f Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 19 Jun 2023 22:43:54 +1000 Subject: [PATCH 06/10] add watch mode to validator --- .../hl7/fhir/validation/ValidationEngine.java | 4 ++ .../fhir/validation/cli/model/CliContext.java | 42 +++++++++++++++++-- .../cli/services/ValidationService.java | 4 +- .../validation/cli/tasks/ValidateTask.java | 3 +- .../hl7/fhir/validation/cli/utils/Params.java | 26 ++++++++++++ .../src/main/resources/help/validate.txt | 14 ++++++- .../fhir/validation/ValidatorCliTests.java | 4 +- 7 files changed, 87 insertions(+), 10 deletions(-) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 505f044c6..922f8fad7 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -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 igs = new ArrayList<>(); @Getter @Setter private List 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 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) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index f3d523dcc..ef4188e59 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -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; + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index deba08fc5..96865dcc4 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -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 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()); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/ValidateTask.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/ValidateTask.java index 0912757c5..4a486d2c6 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/ValidateTask.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/ValidateTask.java @@ -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()); } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 177af45c5..c1c0d919d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -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; diff --git a/org.hl7.fhir.validation/src/main/resources/help/validate.txt b/org.hl7.fhir.validation/src/main/resources/help/validate.txt index fea863e23..f37632bb7 100644 --- a/org.hl7.fhir.validation/src/main/resources/help/validate.txt +++ b/org.hl7.fhir.validation/src/main/resources/help/validate.txt @@ -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 diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ValidatorCliTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ValidatorCliTests.java index b5747140d..04f3454dc 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ValidatorCliTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/ValidatorCliTests.java @@ -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 From ec7b79bc1edc1c7aab52707c06cc66fb0971c9b2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 20 Jun 2023 07:02:26 +1000 Subject: [PATCH 07/10] Fix rendering for unresolvable value sets --- .../renderers/AdditionalBindingsRenderer.java | 2 +- .../hl7/fhir/utilities/xhtml/XhtmlFluent.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java index d3b863d39..92a5651fe 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java @@ -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()); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java index 75a9b456f..ee9b8ee5a 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java @@ -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); } From be01ae2c2152b6a8848a2437107e0f02a3d9cb79 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 20 Jun 2023 07:02:36 +1000 Subject: [PATCH 08/10] Fix for NPE --- .../org/hl7/fhir/validation/cli/services/ValidationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index 96865dcc4..275aee89f 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -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(), null).setAutoFixSliceNames(true).generateSnapshot(base, sd, sd.getUrl(), null, sd.getName()); validator.handleOutput(sd, filename, validator.getVersion()); } } From 9cf8e0368c2153f775f5d6f5121f6330ff1cec06 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 20 Jun 2023 08:36:22 +1000 Subject: [PATCH 09/10] fix up extension context checking --- .../java/org/hl7/fhir/r5/utils/FHIRPathEngine.java | 2 -- .../fhir/validation/instance/InstanceValidator.java | 12 ++++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index cf570ac85..539846276 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -4151,7 +4151,6 @@ public class FHIRPathEngine { String param = nl.get(0).primitiveValue(); List result = new ArrayList(); - if (focus.size() == 1) { String cnt = focus.get(0).primitiveValue(); if ("hex".equals(param)) { @@ -4164,7 +4163,6 @@ public class FHIRPathEngine { result.add(new StringType(new String(enc.decode(cnt)))); } } - return result; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 4e9b9139a..efabcca85 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -2037,10 +2037,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } else if (ctxt.getType() == ExtensionContextType.EXTENSION) { contexts.append("x:" + ctxt.getExpression()); - String ext = stack.getElement().getNamedChildValue("url"); + 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 { + } else if (ext != null) { plist.add(ext); } } else if (ctxt.getType() == ExtensionContextType.FHIRPATH) { From 2607d028a7923f03756a21474f066c3ba5b07139 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 20 Jun 2023 09:15:36 +1000 Subject: [PATCH 10/10] fix compile problem --- .../java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java index 7a375de93..2535b5480 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/BaseWorkerContextTests.java @@ -82,10 +82,6 @@ public class BaseWorkerContextTests { return null; } - @Override - public T fetchResourceRaw(Class class_, String uri) { - return null; - } }; baseWorkerContext.expParameters = new Parameters(); return baseWorkerContext;