diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index de602e213..fd314bfee 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,9 +1,11 @@ Validator: -* no changes with impact +* Add new validation for must-support on types / profiles / targets + improve Extension validation Other code changes: * Ensure "I" flag in profile table representation is not used just for infrastructural constraints * Render multiple values for properties in CodeSystems if they exist * Fix for npe rendering resources based on profiles * fix for use of "current" as version -* hack for past bad package URLs \ No newline at end of file +* hack for past bad package URLs +* Add rendering for must support on types, profiles, targets +* add when rendering turtle to HTML \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 532fef47d..fbb99dd77 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -3245,14 +3245,22 @@ public class ProfileUtilities extends TranslatingUtilities { tl = t; if (t.hasTarget()) { c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null)); + if (isMustSupportDirect(t) && e.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); + } c.getPieces().add(gen.new Piece(null, "(", null)); boolean tfirst = true; - for (UriType u : t.getTargetProfile()) { + for (CanonicalType u : t.getTargetProfile()) { if (tfirst) tfirst = false; else c.addPiece(gen.new Piece(null, " | ", null)); genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue()); + if (isMustSupport(u) && e.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false); + } } c.getPieces().add(gen.new Piece(null, ")", null)); if (t.getAggregation().size() > 0) { @@ -3289,6 +3297,10 @@ public class ProfileUtilities extends TranslatingUtilities { } } else c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null))); + if (isMustSupport(p) && e.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false); + } } } else { String tc = t.getWorkingCode(); @@ -3301,8 +3313,13 @@ public class ProfileUtilities extends TranslatingUtilities { } } else if (pkp != null && pkp.hasLinkFor(tc)) { c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null))); - } else + } else { c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null))); + } + if (isMustSupportDirect(t) && e.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); + } } } return c; @@ -3931,6 +3948,10 @@ public class ProfileUtilities extends TranslatingUtilities { c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null)); else c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null)); + if (isMustSupportDirect(tr) && element.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); + } c.getPieces().add(gen.new Piece(null, "(", null)); } boolean first = true; @@ -3938,6 +3959,10 @@ public class ProfileUtilities extends TranslatingUtilities { if (!first) c.getPieces().add(gen.new Piece(null, " | ", null)); genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue()); + if (isMustSupport(rt) && element.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false); + } first = false; } if (first) @@ -3956,20 +3981,23 @@ public class ProfileUtilities extends TranslatingUtilities { choicerow.getCells().add(gen.new Cell()); choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); - choicerow.getCells().add(gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getType(), null, null)); - // } else if (definitions.getConstraints().contthnsKey(t)) { - // ProfiledType pt = definitions.getConstraints().get(t); - // choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]", Utilities.capitalize(pt.getBaseType())), definitions.getTypes().containsKey(t) ? definitions.getTypes().get(t).getDefinition() : null, null)); - // choicerow.getCells().add(gen.new Cell()); - // choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); - // choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); - // choicerow.getCells().add(gen.new Cell(null, definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null)); + Cell c = gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getType(), null, null); + choicerow.getCells().add(c); + if (isMustSupport(tr) && element.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); + } } else { choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null)); choicerow.getCells().add(gen.new Cell()); choicerow.getCells().add(gen.new Cell(null, null, "", null, null)); choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE); - choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), sd.getType(), null, null)); + Cell c = gen.new Cell(null, pkp.getLinkFor(corePath, t), sd.getType(), null, null); + choicerow.getCells().add(c); + if (isMustSupport(tr) && element.getMustSupport()) { + c.addPiece(gen.new Piece(null, " ", null)); + c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false); + } } if (tr.hasProfile()) { Cell typeCell = choicerow.getCells().get(3); @@ -3982,6 +4010,10 @@ public class ProfileUtilities extends TranslatingUtilities { typeCell.addPiece(gen.new Piece(null, "?gen-e2?", null)); else typeCell.addPiece(gen.new Piece(psd.getUserString("path"), psd.getName(), psd.present())); + if (isMustSupport(pt) && element.getMustSupport()) { + typeCell.addPiece(gen.new Piece(null, " ", null)); + typeCell.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false); + } } typeCell.addPiece(gen.new Piece(null, ")", null)); @@ -6177,6 +6209,32 @@ public class ProfileUtilities extends TranslatingUtilities { return grp; } + public static boolean isMustSupportDirect(TypeRefComponent tr) { + return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))); + } + public static boolean isMustSupport(TypeRefComponent tr) { + if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) { + return true; + } + if (isMustSupport(tr.getProfile())) { + return true; + } + return isMustSupport(tr.getTargetProfile()); + } + + public static boolean isMustSupport(List profiles) { + for (CanonicalType ct : profiles) { + if (isMustSupport(ct)) { + return true; + } + } + return false; + } + + + public static boolean isMustSupport(CanonicalType profile) { + return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT)); + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 40f5f73f6..175fec654 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -180,6 +180,7 @@ public class ToolingExtensions { public static final String EXT_OLD_CONCEPTMAP_EQUIVALENCE = "http://hl7.org/fhir/1.0/StructureDefinition/extension-ConceptMap.element.target.equivalence"; public static final String EXT_EXP_FRAGMENT = "http://hl7.org/fhir/tools/StructureDefinition/expansion-codesystem-fragment"; public static final String EXT_EXP_TOOCOSTLY = "http://hl7.org/fhir/StructureDefinition/valueset-toocostly"; + public static final String EXT_MUST_SUPPORT = "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support"; // specific extension helpers @@ -335,6 +336,8 @@ public class ToolingExtensions { return ((DecimalType) ex.getValue()).asStringValue(); if ((ex.getValue() instanceof MarkdownType)) return ((MarkdownType) ex.getValue()).getValue(); + if ((ex.getValue() instanceof PrimitiveType)) + return ((PrimitiveType) ex.getValue()).primitiveValue(); if (!(ex.getValue() instanceof StringType)) return null; return ((StringType) ex.getValue()).getValue(); 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 1d5359d76..4d9da697d 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 @@ -341,6 +341,8 @@ public class I18nConstants { public static final String RESOURCE_TYPE_MISMATCH_FOR___ = "Resource_type_mismatch_for___"; public static final String SAME_ID_ON_MULTIPLE_ELEMENTS__IN_ = "Same_id_on_multiple_elements__in_"; public static final String SD_MUST_HAVE_DERIVATION = "SD_MUST_HAVE_DERIVATION"; + public static final String SD_NESTED_MUST_SUPPORT_DIFF = "SD_NESTED_MUST_SUPPORT_DIFF"; + public static final String SD_NESTED_MUST_SUPPORT_SNAPSHOT = "SD_NESTED_MUST_SUPPORT_SNAPSHOT"; public static final String SEARCHPARAMETER_BASE_WRONG = "SEARCHPARAMETER_BASE_WRONG"; public static final String SEARCHPARAMETER_EXP_WRONG = "SEARCHPARAMETER_EXP_WRONG"; public static final String SEARCHPARAMETER_NOTFOUND = "SEARCHPARAMETER_NOTFOUND"; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/turtle/Turtle.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/turtle/Turtle.java index a92f03c66..a555698a3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/turtle/Turtle.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/turtle/Turtle.java @@ -384,12 +384,12 @@ public class Turtle { public String asHtml() throws Exception { StringBuilder b = new StringBuilder(); - b.append("
\r\n");
+    b.append("
\r\n");
     commitPrefixes(b);
     for (Section s : sections) {
       commitSection(b, s);
     }
-    b.append("
\r\n"); + b.append("
\r\n"); b.append("\r\n"); return b.toString(); } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 562e34887..18bee95a6 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -608,4 +608,7 @@ REFERENCE_REF_SUSPICIOUS = The syntax of the reference ''{0}'' looks incorrect, TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA = Illegal element name inside in a paragraph in the XHTML (''{0}'') UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE = Unsupported property {3} on type {2} for pattern for discriminator({0}) for slice {1} -UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE = Unsupported: no properties with values found on type {2} for pattern for discriminator({0}) for slice {1} \ No newline at end of file +UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE = Unsupported: no properties with values found on type {2} for pattern for discriminator({0}) for slice {1} +SD_NESTED_MUST_SUPPORT_DIFF = The element {0} has types/profiles/targets that are marked as must support, but the element itself is not marked as must-support. The inner must-supports will be ignored unless the element inherits must-support = true +SD_NESTED_MUST_SUPPORT_SNAPSHOT = The element {0} has types/profiles/targets that are marked as must support, but the element itself is not marked as must-support + \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index 9e1bd5dd3..c36db51d3 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -296,9 +296,10 @@ public class BaseValidator { * Set this parameter to false if the validation does not pass * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ - protected boolean hint(List errors, IssueType type, String path, boolean thePass, String msg) { + protected boolean hint(List errors, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { if (!thePass) { - addValidationMessage(errors, type, -1, -1, path, msg, IssueSeverity.INFORMATION, null); + String message = context.formatMessage(theMessage, theMessageArguments); + addValidationMessage(errors, type, -1, -1, path, message, IssueSeverity.INFORMATION, null); } return thePass; } 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 02aee0df3..0c7ab8330 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 @@ -483,7 +483,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean isKnownExtension(String url) { // Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with - if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") || url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION)) + if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) return true; for (String s : extensionDomains) if (url.startsWith(s)) @@ -1521,6 +1521,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (extensionUrl.equals(profile.getUrl())) { rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), I18nConstants.EXTENSION_EXT_SUBEXTENSION_INVALID, url, profile.getUrl()); } + } else if (SpecialExtensions.isKnownExtension(url)) { + ex = SpecialExtensions.getDefinition(url); } else if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN_NOTHERE, url)) { hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN, url); } @@ -1556,6 +1558,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return ex; } + private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) { for (ElementDefinition ed : profile.getSnapshot().getElement()) { if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) { @@ -1933,7 +1936,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (fetcher != null) { boolean found; try { - found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); + found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url) || + SpecialExtensions.isKnownExtension(url); } catch (IOException e1) { found = false; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/SpecialExtensions.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/SpecialExtensions.java new file mode 100644 index 000000000..a51f75ef1 --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/SpecialExtensions.java @@ -0,0 +1,39 @@ +package org.hl7.fhir.validation.instance; + +import java.io.IOException; +import java.io.InputStream; + +import org.hl7.fhir.convertors.VersionConvertorConstants; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.utilities.Utilities; + +public class SpecialExtensions { + + // copied from R5 spec + private static final String MUST_SUPPORT_SOURCE = "{\"resourceType\" : \"StructureDefinition\",\"id\" : \"elementdefinition-type-must-support\",\"extension\" : [{\"url\" : \"http://hl7.org/fhir/StructureDefinition/structuredefinition-wg\",\"valueCode\" : \"fhir\"},{\"url\" : \"http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm\",\"valueInteger\" : 1}],\"url\" : \"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support\",\"version\" : \"4.5.0\",\"name\" : \"type-must-support\",\"status\" : \"draft\",\"date\" : \"2015-02-28\",\"publisher\" : \"Health Level Seven, Inc. - FHIR Core WG\",\"contact\" : [{\"telecom\" : [{\"system\" : \"url\",\"value\" : \"http://hl7.org/special/committees/FHIR\"}]}],\"description\" : \"If true indicates that the specified type, profile or targetProfile must be supported by implementations.\",\"fhirVersion\" : \"4.5.0\",\"mapping\" : [{\"identity\" : \"rim\",\"uri\" : \"http://hl7.org/v3\",\"name\" : \"RIM Mapping\"}],\"kind\" : \"complex-type\",\"abstract\" : false,\"context\" : [{\"type\" : \"element\",\"expression\" : \"ElementDefinition.type\"},{\"type\" : \"element\",\"expression\" : \"ElementDefinition.type.profile\"},{\"type\" : \"element\",\"expression\" : \"ElementDefinition.type.targetProfile\"}],\"type\" : \"Extension\",\"baseDefinition\" : \"http://hl7.org/fhir/StructureDefinition/Extension\",\"derivation\" : \"constraint\",\"snapshot\" : {\"element\" : [{\"id\" : \"Extension\",\"path\" : \"Extension\",\"short\" : \"The specified type/profile/target must be supported by implementations\",\"definition\" : \"If true indicates that the specified type, profile or targetProfile must be supported by implementations.\",\"comment\" : \"An element may be labelled as must support. This extension clarifies which types/profiles/targetProfiles are must-support. It has no meaning if the element itself is not must-support. If the element is labelled must-support, and none of the options are labelled as must support, then an application must support at least one of the possible options, but is not required to support all of them.\",\"min\" : 0,\"max\" : \"1\",\"base\" : {\"path\" : \"Extension\",\"min\" : 0,\"max\" : \"*\"},\"constraint\" : [{\"key\" : \"ele-1\",\"severity\" : \"error\",\"human\" : \"All FHIR elements must have a @value or children\",\"expression\" : \"hasValue() or (children().count() > id.count())\",\"xpath\" : \"@value|f:*|h:div\",\"source\" : \"http://hl7.org/fhir/StructureDefinition/Element\"},{\"key\" : \"ext-1\",\"severity\" : \"error\",\"human\" : \"Must have either extensions or value[x], not both\",\"expression\" : \"extension.exists() != value.exists()\",\"xpath\" : \"exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])\",\"source\" : \"http://hl7.org/fhir/StructureDefinition/Extension\"}],\"isModifier\" : false},{\"id\" : \"Extension.id\",\"path\" : \"Extension.id\",\"representation\" : [\"xmlAttr\"],\"short\" : \"Unique id for inter-element referencing\",\"definition\" : \"Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.\",\"min\" : 0,\"max\" : \"1\",\"base\" : {\"path\" : \"Element.id\",\"min\" : 0,\"max\" : \"1\"},\"type\" : [{\"extension\" : [{\"url\" : \"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type\",\"valueUri\" : \"string\"}],\"code\" : \"http://hl7.org/fhirpath/System.String\"}],\"isModifier\" : false,\"isSummary\" : false,\"mapping\" : [{\"identity\" : \"rim\",\"map\" : \"n/a\"}]},{\"id\" : \"Extension.extension\",\"path\" : \"Extension.extension\",\"slicing\" : {\"discriminator\" : [{\"type\" : \"value\",\"path\" : \"url\"}],\"description\" : \"Extensions are always sliced by (at least) url\",\"rules\" : \"open\"},\"short\" : \"Extension\",\"definition\" : \"An Extension\",\"min\" : 0,\"max\" : \"0\",\"base\" : {\"path\" : \"Element.extension\",\"min\" : 0,\"max\" : \"*\"},\"type\" : [{\"code\" : \"Extension\"}],\"constraint\" : [{\"key\" : \"ele-1\",\"severity\" : \"error\",\"human\" : \"All FHIR elements must have a @value or children\",\"expression\" : \"hasValue() or (children().count() > id.count())\",\"xpath\" : \"@value|f:*|h:div\",\"source\" : \"http://hl7.org/fhir/StructureDefinition/Element\"},{\"key\" : \"ext-1\",\"severity\" : \"error\",\"human\" : \"Must have either extensions or value[x], not both\",\"expression\" : \"extension.exists() != value.exists()\",\"xpath\" : \"exists(f:extension)!=exists(f:*[starts-with(local-name(.), \\\"value\\\")])\",\"source\" : \"http://hl7.org/fhir/StructureDefinition/Extension\"}],\"isModifier\" : false,\"isSummary\" : false},{\"id\" : \"Extension.url\",\"path\" : \"Extension.url\",\"representation\" : [\"xmlAttr\"],\"short\" : \"identifies the meaning of the extension\",\"definition\" : \"Source of the definition for the extension code - a logical name or a URL.\",\"comment\" : \"The definition may point directly to a computable or human-readable definition of the extensibility codes, or it may be a logical URI as declared in some other specification. The definition SHALL be a URI for the Structure Definition defining the extension.\",\"min\" : 1,\"max\" : \"1\",\"base\" : {\"path\" : \"Extension.url\",\"min\" : 1,\"max\" : \"1\"},\"type\" : [{\"extension\" : [{\"url\" : \"http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type\",\"valueUri\" : \"uri\"}],\"code\" : \"http://hl7.org/fhirpath/System.String\"}],\"fixedUri\" : \"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support\",\"isModifier\" : false,\"isSummary\" : false,\"mapping\" : [{\"identity\" : \"rim\",\"map\" : \"N/A\"}]},{\"id\" : \"Extension.value[x]\",\"path\" : \"Extension.value[x]\",\"short\" : \"Value of extension\",\"definition\" : \"Value of extension - must be one of a constrained set of the data types (see [Extensibility](extensibility.html) for a list).\",\"min\" : 1,\"max\" : \"1\",\"base\" : {\"path\" : \"Extension.value[x]\",\"min\" : 0,\"max\" : \"1\"},\"type\" : [{\"code\" : \"boolean\"}],\"constraint\" : [{\"key\" : \"ele-1\",\"severity\" : \"error\",\"human\" : \"All FHIR elements must have a @value or children\",\"expression\" : \"hasValue() or (children().count() > id.count())\",\"xpath\" : \"@value|f:*|h:div\",\"source\" : \"http://hl7.org/fhir/StructureDefinition/Element\"}],\"isModifier\" : false,\"isSummary\" : false,\"mapping\" : [{\"identity\" : \"rim\",\"map\" : \"N/A\"}]}]},\"differential\" : {\"element\" : [{\"id\" : \"Extension\",\"path\" : \"Extension\",\"short\" : \"The specified type/profile/target must be supported by implementations\",\"definition\" : \"If true indicates that the specified type, profile or targetProfile must be supported by implementations.\",\"comment\" : \"An element may be labelled as must support. This extension clarifies which types/profiles/targetProfiles are must-support. It has no meaning if the element itself is not must-support. If the element is labelled must-support, and none of the options are labelled as must support, then an application must support at least one of the possible options, but is not required to support all of them.\",\"min\" : 0,\"max\" : \"1\"},{\"id\" : \"Extension.extension\",\"path\" : \"Extension.extension\",\"max\" : \"0\"},{\"id\" : \"Extension.url\",\"path\" : \"Extension.url\",\"fixedUri\" : \"http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support\"},{\"id\" : \"Extension.value[x]\",\"path\" : \"Extension.value[x]\",\"min\" : 1,\"type\" : [{\"code\" : \"boolean\"}]}]}}"; + + public static boolean isKnownExtension(String url) { + return Utilities.existsInList(url, + "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support" + ); + } + + public static StructureDefinition getDefinition(String url) { + try { + switch (url) { + case "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support" : return makeMustSupport(); + default: return null; + } + } catch (Exception e) { + return null; + } + } + + private static StructureDefinition makeMustSupport() throws FHIRFormatError, IOException { + return (StructureDefinition) new JsonParser().parse(MUST_SUPPORT_SOURCE); + } + +} 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 d7dec41cd..f7631ef8b 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 @@ -28,6 +28,7 @@ import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.SearchParameter; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -95,6 +96,69 @@ public class StructureDefinitionValidator extends BaseValidator { } catch (FHIRException | IOException e) { rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); } + List differentials = src.getChildrenByName("differential"); + List snapshots = src.getChildrenByName("snapshot"); + for (Element differential : differentials) { + validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0); + } + for (Element snapshot : snapshots) { + validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true); + } + } + + private void validateElementList(List errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot) { + List elements = elementList.getChildrenByName("element"); + int cc = 0; + for (Element element : elements) { + validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot); + cc++; + } + } + + private void validateElementDefinition(List errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot) { + boolean typeMustSupport = false; + List types = element.getChildrenByName("type"); + for (Element type : types) { + if (hasMustSupportExtension(type)) { + typeMustSupport = true; + } + } + if (typeMustSupport) { + if (snapshot) { + rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_SNAPSHOT, element.getNamedChildValue("path")); + } else { + hint(errors, IssueType.EXCEPTION, stack.getLiteralPath(), hasSnapshot || "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_DIFF, element.getNamedChildValue("path")); + } + } + } + + private boolean hasMustSupportExtension(Element type) { + if ("true".equals(getExtensionValue(type, ToolingExtensions.EXT_MUST_SUPPORT))) { + return true; + } + List profiles = type.getChildrenByName("profile"); + for (Element profile : profiles) { + if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) { + return true; + } + } + profiles = type.getChildrenByName("targetProfile"); + for (Element profile : profiles) { + if ("true".equals(getExtensionValue(profile, ToolingExtensions.EXT_MUST_SUPPORT))) { + return true; + } + } + return false; + } + + private String getExtensionValue(Element element, String url) { + List extensions = element.getChildrenByName("extension"); + for (Element extension : extensions) { + if (url.equals(extension.getNamedChildValue("url"))) { + return extension.getNamedChildValue("value"); + } + } + return null; } private StructureDefinition loadAsSD(Element src) throws FHIRException, IOException {