diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/ConvertedStructureDefinitionValidationTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/ConvertedStructureDefinitionValidationTest.java new file mode 100644 index 00000000000..b997cdeafec --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/ConvertedStructureDefinitionValidationTest.java @@ -0,0 +1,70 @@ +package org.hl7.fhir.r4.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.test.BaseTest; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * See #6424 + */ +public class ConvertedStructureDefinitionValidationTest extends BaseTest { + + private static final Logger ourLog = LoggerFactory.getLogger(ConvertedStructureDefinitionValidationTest.class); + private final FhirContext myCtx = FhirContext.forR4Cached(); + + @Test + public void testInvalidateCacheThenValidate() throws Exception { + + // Create a validation support that loads the patient-citizenship extension + PrePopulatedValidationSupport val = new PrePopulatedValidationSupport(myCtx); + val.addStructureDefinition(loadResource(myCtx, StructureDefinition.class, "r4/extension-patient-citizenship.json")); + + IValidationSupport builtInValidationSupport = myCtx.getValidationSupport(); + ValidationSupportChain validationSupport = new ValidationSupportChain(val, builtInValidationSupport); + FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupport); + instanceValidator.setBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore); + + FhirValidator validator = myCtx.newValidator(); + validator.registerValidatorModule(instanceValidator); + + // Create a patient with an invalid citizenship extension + Patient patient = new Patient(); + patient.setActive(true); + + Extension citizenship = patient.addExtension(); + citizenship.setUrl("http://hl7.org/fhir/StructureDefinition/patient-citizenship"); + citizenship.addExtension("period", new StringType("NOT A PERIOD")); + + // Validate once + ValidationResult outcome = validator.validateWithResult(patient); + ourLog.info(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.toOperationOutcome())); + assertEquals("The Profile 'http://hl7.org/fhir/StructureDefinition/patient-citizenship|5.1.0' definition allows for the type Period but found type string", outcome.getMessages().get(0).getMessage()); + assertEquals(1, outcome.getMessages().size()); + + // Invalidate the caches + instanceValidator.invalidateCaches(); + + // Validate a second time + outcome = validator.validateWithResult(patient); + ourLog.info(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.toOperationOutcome())); + assertEquals("The Profile 'http://hl7.org/fhir/StructureDefinition/patient-citizenship|5.1.0' definition allows for the type Period but found type string", outcome.getMessages().get(0).getMessage()); + assertEquals(1, outcome.getMessages().size()); + + } + +} diff --git a/hapi-fhir-validation/src/test/resources/r4/extension-patient-citizenship.json b/hapi-fhir-validation/src/test/resources/r4/extension-patient-citizenship.json new file mode 100644 index 00000000000..24705788be2 --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/r4/extension-patient-citizenship.json @@ -0,0 +1,766 @@ +{ + "resourceType" : "StructureDefinition", + "id" : "patient-citizenship", + "url" : "http://hl7.org/fhir/StructureDefinition/patient-citizenship", + "identifier" : [{ + "system" : "urn:ietf:rfc:3986", + "value" : "urn:oid:2.16.840.1.113883.4.642.5.1330" + }], + "version" : "5.1.0", + "name" : "PatCitizenship", + "title" : "Patient Citizenship", + "status" : "active", + "experimental" : false, + "date" : "2020-12-28T16:55:11+11:00", + "publisher" : "HL7 International / Patient Administration", + "contact" : [{ + "telecom" : [{ + "system" : "url", + "value" : "http://www.hl7.org/Special/committees/pafm" + }] + }], + "description" : "The patient's legal status as citizen of a country.", + "jurisdiction" : [{ + "coding" : [{ + "system" : "http://unstats.un.org/unsd/methods/m49/m49.htm", + "code" : "001" + }] + }], + "fhirVersion" : "5.0.0", + "mapping" : [{ + "identity" : "rim", + "uri" : "http://hl7.org/v3", + "name" : "RIM Mapping" + }], + "kind" : "complex-type", + "abstract" : false, + "context" : [{ + "type" : "element", + "expression" : "Patient" + }], + "type" : "Extension", + "baseDefinition" : "http://hl7.org/fhir/StructureDefinition/Extension", + "derivation" : "constraint", + "snapshot" : { + "element" : [{ + "id" : "Extension", + "path" : "Extension", + "short" : "Nation(s) where the patient claims citizenship", + "definition" : "The patient's legal status as citizen of a country.", + "min" : 0, + "max" : "*", + "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())", + "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()", + "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", + "valueUrl" : "id" + }], + "code" : "http://hl7.org/fhirpath/System.String" + }], + "condition" : ["ele-1"], + "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" : "Additional content defined by implementations", + "definition" : "May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and managable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.", + "comment" : "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions. The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.", + "alias" : ["extensions", + "user content"], + "min" : 0, + "max" : "*", + "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())", + "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()", + "source" : "http://hl7.org/fhir/StructureDefinition/Extension" + }], + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "n/a" + }] + }, + { + "id" : "Extension.extension:code", + "path" : "Extension.extension", + "sliceName" : "code", + "short" : "Nation code of citizenship", + "definition" : "Nation code representing the citizenship of patient.", + "min" : 0, + "max" : "1", + "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())", + "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()", + "source" : "http://hl7.org/fhir/StructureDefinition/Extension" + }], + "isModifier" : false, + "isSummary" : false + }, + { + "id" : "Extension.extension:code.id", + "path" : "Extension.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", + "valueUrl" : "id" + }], + "code" : "http://hl7.org/fhirpath/System.String" + }], + "condition" : ["ele-1"], + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "n/a" + }] + }, + { + "id" : "Extension.extension:code.extension", + "path" : "Extension.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())", + "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()", + "source" : "http://hl7.org/fhir/StructureDefinition/Extension" + }], + "isModifier" : false, + "isSummary" : false + }, + { + "id" : "Extension.extension:code.url", + "path" : "Extension.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" : [{ + "code" : "uri" + }], + "fixedUri" : "code", + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "N/A" + }] + }, + { + "id" : "Extension.extension:code.value[x]", + "path" : "Extension.extension.value[x]", + "short" : "Value of extension", + "definition" : "Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R5/extensibility.html) for a list).", + "min" : 1, + "max" : "1", + "base" : { + "path" : "Extension.value[x]", + "min" : 0, + "max" : "1" + }, + "type" : [{ + "code" : "CodeableConcept" + }], + "condition" : ["ext-1"], + "constraint" : [{ + "key" : "ele-1", + "severity" : "error", + "human" : "All FHIR elements must have a @value or children", + "expression" : "hasValue() or (children().count() > id.count())", + "source" : "http://hl7.org/fhir/StructureDefinition/Element" + }], + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "N/A" + }] + }, + { + "id" : "Extension.extension:period", + "path" : "Extension.extension", + "sliceName" : "period", + "short" : "Time period of citizenship", + "definition" : "Period when citizenship was effective.", + "min" : 0, + "max" : "1", + "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())", + "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()", + "source" : "http://hl7.org/fhir/StructureDefinition/Extension" + }], + "isModifier" : false, + "isSummary" : false + }, + { + "id" : "Extension.extension:period.id", + "path" : "Extension.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", + "valueUrl" : "id" + }], + "code" : "http://hl7.org/fhirpath/System.String" + }], + "condition" : ["ele-1"], + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "n/a" + }] + }, + { + "id" : "Extension.extension:period.extension", + "path" : "Extension.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())", + "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()", + "source" : "http://hl7.org/fhir/StructureDefinition/Extension" + }], + "isModifier" : false, + "isSummary" : false + }, + { + "id" : "Extension.extension:period.url", + "path" : "Extension.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" : [{ + "code" : "uri" + }], + "fixedUri" : "period", + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "N/A" + }] + }, + { + "id" : "Extension.extension:period.value[x]", + "path" : "Extension.extension.value[x]", + "short" : "Value of extension", + "definition" : "Value of extension - must be one of a constrained set of the data types (see [Extensibility](http://hl7.org/fhir/R5/extensibility.html) for a list).", + "min" : 1, + "max" : "1", + "base" : { + "path" : "Extension.value[x]", + "min" : 0, + "max" : "1" + }, + "type" : [{ + "code" : "Period" + }], + "condition" : ["ext-1"], + "constraint" : [{ + "key" : "ele-1", + "severity" : "error", + "human" : "All FHIR elements must have a @value or children", + "expression" : "hasValue() or (children().count() > id.count())", + "source" : "http://hl7.org/fhir/StructureDefinition/Element" + }], + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "N/A" + }] + }, + { + "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", + "valueUrl" : "uri" + }], + "code" : "http://hl7.org/fhirpath/System.String" + }], + "fixedUri" : "http://hl7.org/fhir/StructureDefinition/patient-citizenship", + "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](http://hl7.org/fhir/R5/extensibility.html) for a list).", + "min" : 0, + "max" : "0", + "base" : { + "path" : "Extension.value[x]", + "min" : 0, + "max" : "1" + }, + "type" : [{ + "code" : "base64Binary" + }, + { + "code" : "boolean" + }, + { + "code" : "canonical" + }, + { + "code" : "code" + }, + { + "code" : "date" + }, + { + "code" : "dateTime" + }, + { + "code" : "decimal" + }, + { + "code" : "id" + }, + { + "code" : "instant" + }, + { + "code" : "integer" + }, + { + "code" : "integer64" + }, + { + "code" : "markdown" + }, + { + "code" : "oid" + }, + { + "code" : "positiveInt" + }, + { + "code" : "string" + }, + { + "code" : "time" + }, + { + "code" : "unsignedInt" + }, + { + "code" : "uri" + }, + { + "code" : "url" + }, + { + "code" : "uuid" + }, + { + "code" : "Address" + }, + { + "code" : "Age" + }, + { + "code" : "Annotation" + }, + { + "code" : "Attachment" + }, + { + "code" : "CodeableConcept" + }, + { + "code" : "CodeableReference" + }, + { + "code" : "Coding" + }, + { + "code" : "ContactPoint" + }, + { + "code" : "Count" + }, + { + "code" : "Distance" + }, + { + "code" : "Duration" + }, + { + "code" : "HumanName" + }, + { + "code" : "Identifier" + }, + { + "code" : "Money" + }, + { + "code" : "Period" + }, + { + "code" : "Quantity" + }, + { + "code" : "Range" + }, + { + "code" : "Ratio" + }, + { + "code" : "RatioRange" + }, + { + "code" : "Reference" + }, + { + "code" : "SampledData" + }, + { + "code" : "Signature" + }, + { + "code" : "Timing" + }, + { + "code" : "ContactDetail" + }, + { + "code" : "DataRequirement" + }, + { + "code" : "Expression" + }, + { + "code" : "ParameterDefinition" + }, + { + "code" : "RelatedArtifact" + }, + { + "code" : "TriggerDefinition" + }, + { + "code" : "UsageContext" + }, + { + "code" : "Availability" + }, + { + "code" : "ExtendedContactDetail" + }, + { + "code" : "Dosage" + }, + { + "code" : "Meta" + }], + "condition" : ["ext-1"], + "constraint" : [{ + "key" : "ele-1", + "severity" : "error", + "human" : "All FHIR elements must have a @value or children", + "expression" : "hasValue() or (children().count() > id.count())", + "source" : "http://hl7.org/fhir/StructureDefinition/Element" + }], + "isModifier" : false, + "isSummary" : false, + "mapping" : [{ + "identity" : "rim", + "map" : "N/A" + }] + }] + }, + "differential" : { + "element" : [{ + "id" : "Extension", + "path" : "Extension", + "short" : "Nation(s) where the patient claims citizenship", + "definition" : "The patient's legal status as citizen of a country.", + "min" : 0, + "max" : "*" + }, + { + "id" : "Extension.extension:code", + "path" : "Extension.extension", + "sliceName" : "code", + "short" : "Nation code of citizenship", + "definition" : "Nation code representing the citizenship of patient.", + "min" : 0, + "max" : "1", + "type" : [{ + "code" : "Extension" + }] + }, + { + "id" : "Extension.extension:code.extension", + "path" : "Extension.extension.extension", + "max" : "0" + }, + { + "id" : "Extension.extension:code.url", + "path" : "Extension.extension.url", + "type" : [{ + "code" : "uri" + }], + "fixedUri" : "code" + }, + { + "id" : "Extension.extension:code.value[x]", + "path" : "Extension.extension.value[x]", + "min" : 1, + "type" : [{ + "code" : "CodeableConcept" + }] + }, + { + "id" : "Extension.extension:period", + "path" : "Extension.extension", + "sliceName" : "period", + "short" : "Time period of citizenship", + "definition" : "Period when citizenship was effective.", + "min" : 0, + "max" : "1", + "type" : [{ + "code" : "Extension" + }] + }, + { + "id" : "Extension.extension:period.extension", + "path" : "Extension.extension.extension", + "max" : "0" + }, + { + "id" : "Extension.extension:period.url", + "path" : "Extension.extension.url", + "type" : [{ + "code" : "uri" + }], + "fixedUri" : "period" + }, + { + "id" : "Extension.extension:period.value[x]", + "path" : "Extension.extension.value[x]", + "min" : 1, + "type" : [{ + "code" : "Period" + }] + }, + { + "id" : "Extension.url", + "path" : "Extension.url", + "fixedUri" : "http://hl7.org/fhir/StructureDefinition/patient-citizenship" + }, + { + "id" : "Extension.value[x]", + "path" : "Extension.value[x]", + "min" : 0, + "max" : "0" + }] + } +}