diff --git a/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java b/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java index d1a05615d4a..9d0d1a38a4c 100644 --- a/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java +++ b/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java @@ -24,18 +24,15 @@ package org.hl7.fhir.convertors; import java.util.ArrayList; import java.util.List; -import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.ContactDetail; import org.hl7.fhir.dstu3.model.Contributor.ContributorType; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; import org.hl7.fhir.dstu3.model.ExpansionProfile.DesignationIncludeDesignationComponent; import org.hl7.fhir.dstu3.model.ExpansionProfile.SystemVersionProcessingMode; -import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Expression.ExpressionLanguage; import org.hl7.fhir.r4.model.Questionnaire.EnableWhenBehavior; import org.hl7.fhir.r4.model.BooleanType; -import org.hl7.fhir.r4.model.Contributor; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Type; diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java index 78911bf2b92..8f296eedb84 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java @@ -8,6 +8,8 @@ import org.hl7.fhir.r4.elementmodel.Element; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Questionnaire.*; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; + /** * Evaluates Questionnaire.item.enableWhen against a QuestionnaireResponse. * Ignores possible modifierExtensions and extensions. @@ -53,7 +55,7 @@ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator { if (operator == QuestionnaireItemOperator.EXISTS){ Type answer = enableCondition.getAnswer(); if (!(answer instanceof BooleanType)){ - throw new RuntimeException("Exists-operator requires answerBoolean"); + throw new UnprocessableEntityException("Exists-operator requires answerBoolean"); } return new EnableWhenResult(((BooleanType)answer).booleanValue() != answerItems.isEmpty(), linkId, enableCondition, questionnaireResponse); @@ -69,45 +71,62 @@ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator { try { actualAnswer = answer.asType(); } catch (FHIRException e) { - throw new RuntimeException("Unexpected answer type", e); + throw new UnprocessableEntityException("Unexpected answer type", e); } if (!actualAnswer.getClass().equals(expectedAnswer.getClass())) { - throw new RuntimeException("Expected answer and actual answer have incompatible types"); + throw new UnprocessableEntityException("Expected answer and actual answer have incompatible types"); } if (expectedAnswer instanceof Coding) { return compareCodingAnswer((Coding)expectedAnswer, (Coding)actualAnswer, questionnaireItemOperator); } else if ((expectedAnswer instanceof PrimitiveType)) { return comparePrimitiveAnswer((PrimitiveType)actualAnswer, (PrimitiveType)expectedAnswer, questionnaireItemOperator); + } else if (expectedAnswer instanceof Quantity) { + return compareQuantityAnswer((Quantity)expectedAnswer, (Quantity)actualAnswer, questionnaireItemOperator); } - // TODO: Quantity, Attachment, reference? - throw new RuntimeException("Unimplemented answer type: " + expectedAnswer.getClass()); + // TODO: Attachment, reference? + throw new UnprocessableEntityException("Unimplemented answer type: " + expectedAnswer.getClass()); } - private boolean comparePrimitiveAnswer(PrimitiveType actualAnswer, PrimitiveType expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) { + + + private boolean compareQuantityAnswer(Quantity actualAnswer, Quantity expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) { + return compareComparable(actualAnswer.getValue(), expectedAnswer.getValue(), questionnaireItemOperator); + } + + + private boolean comparePrimitiveAnswer(PrimitiveType actualAnswer, PrimitiveType expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) { if (actualAnswer.getValue() instanceof Comparable){ - @SuppressWarnings({ "rawtypes", "unchecked" }) - int result = ((Comparable)actualAnswer.getValue()).compareTo(expectedAnswer.getValue()); - if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){ - return result == 0; - } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){ - return result != 0; - } else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_OR_EQUAL){ - return result >= 0; - } else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_OR_EQUAL){ - return result <= 0; - } else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_THAN){ - return result < 0; - } else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_THAN){ - return result > 0; - } - throw new RuntimeException("Bad operator for PrimitiveType comparison"); + return compareComparable((Comparable)actualAnswer.getValue(), (Comparable) expectedAnswer.getValue(), questionnaireItemOperator); } else if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){ return actualAnswer.equalsShallow(expectedAnswer); } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){ return !actualAnswer.equalsShallow(expectedAnswer); } - throw new RuntimeException("Bad operator for PrimitiveType comparison"); + throw new UnprocessableEntityException("Bad operator for PrimitiveType comparison"); } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean compareComparable(Comparable actual, Comparable expected, + QuestionnaireItemOperator questionnaireItemOperator) { + int result = actual.compareTo(expected); + + if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){ + return result == 0; + } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){ + return result != 0; + } else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_OR_EQUAL){ + return result >= 0; + } else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_OR_EQUAL){ + return result <= 0; + } else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_THAN){ + return result < 0; + } else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_THAN){ + return result > 0; + } + + throw new UnprocessableEntityException("Bad operator for PrimitiveType comparison"); + + } + private List findQuestionAnswers(Element questionnaireResponse, String question) { List matchingItems = questionnaireResponse.getChildren(ITEM_ELEMENT) .stream() @@ -134,7 +153,7 @@ public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator { } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){ return result == false; } - throw new RuntimeException("Bad operator for Coding comparison"); + throw new UnprocessableEntityException("Bad operator for Coding comparison"); } private boolean compareCodes(Coding expectedCoding, Coding value) { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java index 34a6d9b76a3..13f79bdca7d 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java @@ -328,12 +328,12 @@ public class QuestionnaireResponseValidatorDstu3Test { } @Test - public void testRequiredQuestionWithEnableWhenHidesQuestion() { + public void testRequiredQuestionQuantityWithEnableWhenHidesQuestionHasAnswerTrue() { Questionnaire q = new Questionnaire(); - q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING); + q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY); - // create the questionnaire + //link1 question is enabled when link0 has answer QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); item1.setLinkId("link1").setRequired(true); q.addItem(item1); @@ -341,12 +341,11 @@ public class QuestionnaireResponseValidatorDstu3Test { item1.addEnableWhen(enable); enable.setQuestion("link0"); enable.setHasAnswer(true); + enable.setAnswer(new Quantity().setValue(1L)); - QuestionnaireResponse qa = new QuestionnaireResponse(); qa.setStatus(QuestionnaireResponseStatus.COMPLETED); qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1"); - //qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO")); String reference = qa.getQuestionnaire().getReference(); when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); @@ -356,6 +355,62 @@ public class QuestionnaireResponseValidatorDstu3Test { assertThat(errors.toString(), containsString("No issues")); } + @Test + public void testRequiredQuestionQuantityWithEnableWhenHidesQuestionValue() { + + Questionnaire q = new Questionnaire(); + q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY); + + //link1 question is enabled when link0 has answer + QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); + item1.setLinkId("link1").setRequired(true); + q.addItem(item1); + QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent(); + item1.addEnableWhen(enable); + enable.setQuestion("link0"); + enable.setAnswer(new Quantity().setValue(2L)); + + QuestionnaireResponse qa = new QuestionnaireResponse(); + qa.setStatus(QuestionnaireResponseStatus.COMPLETED); + qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1"); + qa.addItem().setLinkId("link0").addAnswer().setValue(new Quantity().setValue(1L)); + + String reference = qa.getQuestionnaire().getReference(); + when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qa); + + ourLog.info(errors.toString()); + assertThat(errors.toString(), containsString("No issues")); + } + + @Test + public void testRequiredQuestionQuantityWithEnableWhenEnablesQuestionValue() { + + Questionnaire q = new Questionnaire(); + q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY); + + //link1 question is enabled when link0 has answer + QuestionnaireItemComponent item1 = new QuestionnaireItemComponent(); + item1.setLinkId("link1").setRequired(true); + q.addItem(item1); + QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent(); + item1.addEnableWhen(enable); + enable.setQuestion("link0"); + enable.setAnswer(new Quantity().setValue(1L)); + + QuestionnaireResponse qa = new QuestionnaireResponse(); + qa.setStatus(QuestionnaireResponseStatus.COMPLETED); + qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1"); + qa.addItem().setLinkId("link0").addAnswer().setValue(new Quantity().setValue(1L)); + + String reference = qa.getQuestionnaire().getReference(); + when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); + ValidationResult errors = myVal.validateWithResult(qa); + + ourLog.info(errors.toString()); + assertThat(errors.toString(), containsString("No response found for required item link1")); + } + @Test public void testRequiredQuestionWithEnableWhenHasAnswerTrueWithAnswer() { @@ -451,7 +506,6 @@ public class QuestionnaireResponseValidatorDstu3Test { when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q); ValidationResult errors = myVal.validateWithResult(qr); - assertThat(errors.toString(), Matchers.not(containsString("No issues"))); }