diff --git a/shr/src/main/java/org/freeshr/validations/FhirResourceValidator.java b/shr/src/main/java/org/freeshr/validations/FhirResourceValidator.java index 08773c3c..c3e71155 100644 --- a/shr/src/main/java/org/freeshr/validations/FhirResourceValidator.java +++ b/shr/src/main/java/org/freeshr/validations/FhirResourceValidator.java @@ -19,6 +19,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Component public class FhirResourceValidator { @@ -27,8 +29,8 @@ public class FhirResourceValidator { private TRConceptValidator trConceptValidator; private volatile FhirValidator fhirValidator; private ShrProfileValidationSupport shrProfileValidationSupport; - private List resourceFieldErrors = new ArrayList<>(); - private Map extensionFieldErrors = new HashMap<>(); + private List resourceFieldErrors = new ArrayList<>(); + private Map extensionFieldErrors = new HashMap<>(); @Autowired public FhirResourceValidator(FhirFeedUtil fhirUtil, TRConceptValidator trConceptValidator, ShrProfileValidationSupport shrProfileValidationSupport) { @@ -39,12 +41,13 @@ public FhirResourceValidator(FhirFeedUtil fhirUtil, TRConceptValidator trConcept } private void initFieldErrorChecks() { - this.resourceFieldErrors.add("/f:Bundle/f:entry/f:resource/f:Condition/f:category"); - this.resourceFieldErrors.add("/f:Bundle/f:entry/f:resource/f:Condition/f:code/f:coding"); - this.resourceFieldErrors.add("/f:Bundle/f:entry/f:resource/f:Condition/f:clinicalStatus"); + this.resourceFieldErrors.add(Pattern.compile("/f:Bundle/f:entry(\\[\\d+\\])*/f:resource/f:Condition/f:category")); + this.resourceFieldErrors.add(Pattern.compile("/f:Bundle/f:entry(\\[\\d+\\])*/f:resource/f:Condition/f:code/f:coding")); + this.resourceFieldErrors.add(Pattern.compile("/f:Bundle/f:entry(\\[\\d+\\])*/f:resource/f:Condition/f:clinicalStatus")); - this.extensionFieldErrors.put("/f:Bundle/f:entry/f:resource/f:MedicationOrder/f:dosageInstruction/f:timing/f:extension", "https://sharedhealth.atlassian.net/wiki/display/docs/fhir-extensions#TimingScheduledDate"); - this.extensionFieldErrors.put("/f:Bundle/f:entry/f:resource/f:MedicationOrder/f:dosageInstruction/f:extension", "https://sharedhealth.atlassian.net/wiki/display/docs/fhir-extensions#DosageInstructionCustomDosage"); + this.extensionFieldErrors.put(Pattern.compile("/f:Bundle/f:entry(\\[\\d+\\])*/f:resource/f:MedicationOrder/f:dosageInstruction(\\[\\d+\\])*/f:timing/f:extension(\\[\\d+\\])*"), "https://sharedhealth.atlassian.net/wiki/display/docs/fhir-extensions#TimingScheduledDate"); + this.extensionFieldErrors.put(Pattern.compile("/f:Bundle/f:entry(\\[\\d+\\])*/f:resource/f:MedicationOrder/f:dosageInstruction(\\[\\d+\\])*/f:extension(\\[\\d+\\])*"), "https://sharedhealth.atlassian.net/wiki/display/docs/fhir-extensions#DosageInstructionCustomDosage"); + this.extensionFieldErrors.put(Pattern.compile("/f:Bundle/f:entry(\\[\\d+\\])*/f:resource/f:MedicationOrder/f:extension(\\[\\d+\\])*"), "https://sharedhealth.atlassian.net/wiki/display/docs/fhir-extensions#MedicationOrderAction"); } public FhirValidationResult validate(Bundle bundle) { @@ -62,8 +65,8 @@ private void checkValidationResult(FhirValidationResult validationResult) { private void checkForExtensionErrors(FhirValidationResult validationResult) { for (SingleValidationMessage validationMessage : validationResult.getMessages()) { - if (extensionFieldErrors.containsKey(validationMessage.getLocationString()) - && validationMessage.getMessage().contains(extensionFieldErrors.get(validationMessage.getLocationString()))) { + String extensionUrlForLocation = getExtensionForLocationError(validationMessage.getLocationString()); + if (extensionUrlForLocation != null && validationMessage.getMessage().contains(extensionUrlForLocation)) { if (validationMessage.getSeverity().ordinal() >= ResultSeverityEnum.ERROR.ordinal()) { validationMessage.setSeverity(ResultSeverityEnum.WARNING); } @@ -73,7 +76,7 @@ private void checkForExtensionErrors(FhirValidationResult validationResult) { private void checkForConditionErrors(FhirValidationResult validationResult) { for (SingleValidationMessage validationMessage : validationResult.getMessages()) { - if (resourceFieldErrors.contains(validationMessage.getLocationString())) { + if (isPossibleResourceFieldError(validationMessage.getLocationString())) { if (validationMessage.getSeverity().ordinal() <= ResultSeverityEnum.WARNING.ordinal()) { validationMessage.setSeverity(ResultSeverityEnum.ERROR); } @@ -81,6 +84,22 @@ private void checkForConditionErrors(FhirValidationResult validationResult) { } } + private String getExtensionForLocationError(String locationString) { + for (Pattern extensionFieldErrorLocationPattern : extensionFieldErrors.keySet()) { + Matcher matcher = extensionFieldErrorLocationPattern.matcher(locationString); + if (matcher.matches()) return extensionFieldErrors.get(extensionFieldErrorLocationPattern); + } + return null; + } + + private boolean isPossibleResourceFieldError(String locationString) { + for (Pattern resourceFieldErrorPattern : resourceFieldErrors) { + Matcher matcher = resourceFieldErrorPattern.matcher(locationString); + if (matcher.matches()) return true; + } + return false; + } + /** * This is required since the InstanceValidator does not raise a severity.error on concept validation failure. * InstanceValidator.checkCodeableConcept() line number 225 @@ -98,9 +117,10 @@ private void checkForConceptValidationError(FhirValidationResult validationResul } private static String getTerminologySystem(String message) { - if (message.contains("Unable to validate code")) { - String substring = message.substring(message.indexOf("in code system")); - return StringUtils.remove(StringUtils.removeStart(substring, "in code system"), "\""); + Pattern pattern = Pattern.compile("Unable to validate code \"(.*)\" in code system \"(?.*)\""); + Matcher matcher = pattern.matcher(message); + if(matcher.matches()) { + return matcher.group("TRSERVERURL"); } return ""; } diff --git a/shr/src/test/java/org/freeshr/application/fhir/EncounterValidatorIntegrationTest.java b/shr/src/test/java/org/freeshr/application/fhir/EncounterValidatorIntegrationTest.java index b6550db9..10a2921e 100644 --- a/shr/src/test/java/org/freeshr/application/fhir/EncounterValidatorIntegrationTest.java +++ b/shr/src/test/java/org/freeshr/application/fhir/EncounterValidatorIntegrationTest.java @@ -273,13 +273,19 @@ public void shouldRejectEncounterWithInvalidDiagnosisCategoryAndStatusAndSystem( FileUtil.asString("xmls/encounters/dstu2/p98001046534_encounter_with_localRefs_invalidCondition.xml")); validationContext = new EncounterValidationContext(encounterBundle, new FhirFeedUtil()); EncounterValidationResponse response = validator.validate(validationContext); + assertEquals(6, response.getErrors().size()); assertFailureInResponse("/f:Bundle/f:entry/f:resource/f:Condition/f:code/f:coding/f:system", "@value cannot be empty", false, response); + assertFailureInResponse("/f:Bundle/f:entry[3]/f:resource/f:Condition/f:code/f:coding/f:system", + "@value cannot be empty", false, response); assertFailureInResponse("/f:Bundle/f:entry/f:resource/f:Condition/f:category", "None of the codes are in the example value set http://hl7.org/fhir/ValueSet/condition-category", true, response); + assertFailureInResponse("/f:Bundle/f:entry[3]/f:resource/f:Condition/f:category", + "None of the codes are in the example value set http://hl7.org/fhir/ValueSet/condition-category", true, response); assertFailureInResponse("/f:Bundle/f:entry/f:resource/f:Condition/f:clinicalStatus", "Coded value wrong is not in value set http://hl7.org/fhir/ValueSet/condition-clinical", true, response); - assertEquals(3, response.getErrors().size()); + assertFailureInResponse("/f:Bundle/f:entry[3]/f:resource/f:Condition/f:clinicalStatus", + "Coded value wrong is not in value set http://hl7.org/fhir/ValueSet/condition-clinical", true, response); } @Test @@ -325,7 +331,7 @@ public void shouldRejectInvalidRelationshipTypeInFamilyMemberHistory() throws Ex assertFalse(response.isSuccessful()); assertEquals(1, response.getErrors().size()); assertFailureInResponse("/f:Bundle/f:entry/f:resource/f:FamilyMemberHistory/f:relationship", - "Unable to validate code \"FT\" in code system \"http://localhost:9997/openmrs/ws/rest/v1/tr/vs/Relationship-Type\"", true, response); + "Unable to validate code \"INVALID\" in code system \"http://localhost:9997/openmrs/ws/rest/v1/tr/vs/Relationship-Type\"", true, response); } @Test @@ -343,7 +349,6 @@ public void shouldValidateMedicationOrderWithCustomDosageExtension() throws Exce FileUtil.asString("xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_custom_dosage.xml")); validationContext = new EncounterValidationContext(encounterBundle, new FhirFeedUtil()); EncounterValidationResponse response = validator.validate(validationContext); - debugEncounterValidationResponse(response); assertTrue(response.isSuccessful()); } diff --git a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_family_member_history_relationship_invalid.xml b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_family_member_history_relationship_invalid.xml index 17f9f247..2c0af07c 100644 --- a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_family_member_history_relationship_invalid.xml +++ b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_family_member_history_relationship_invalid.xml @@ -92,7 +92,7 @@ - + diff --git a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_localRefs_invalidCondition.xml b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_localRefs_invalidCondition.xml index 25909a33..ec9acb37 100644 --- a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_localRefs_invalidCondition.xml +++ b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_localRefs_invalidCondition.xml @@ -39,6 +39,12 @@ +
+ + + + +
@@ -76,6 +82,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_custom_dosage.xml b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_custom_dosage.xml index aae8308e..c47a1825 100644 --- a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_custom_dosage.xml +++ b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_custom_dosage.xml @@ -81,6 +81,10 @@ + + + diff --git a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_invalid_medication.xml b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_invalid_medication.xml index 3f6ff7c0..c6e5fece 100644 --- a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_invalid_medication.xml +++ b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_invalid_medication.xml @@ -81,6 +81,10 @@ + + + diff --git a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_scheduled_date.xml b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_scheduled_date.xml index 3727ff3d..a003a69a 100644 --- a/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_scheduled_date.xml +++ b/shr/src/test/resources/xmls/encounters/dstu2/p98001046534_encounter_with_medication_order_scheduled_date.xml @@ -39,6 +39,12 @@
+
+ + + + +
@@ -76,11 +82,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -121,7 +186,7 @@ - +