Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip hidden field from validation #1610

Merged
merged 16 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.android.fhir.datacapture.validation

import android.content.Context
import com.google.android.fhir.datacapture.isHidden
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

Expand All @@ -39,7 +40,12 @@ internal object QuestionnaireResponseItemValidator {
answers: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>,
context: Context
): ValidationResult {
val validationResults = validators.map { it.validate(questionnaireItem, answers, context) }
val validationResults =
validators.map {
if (questionnaireItem.isHidden)
ConstraintValidator.ConstraintValidationResult(true, "Field is hidden")
jingtang10 marked this conversation as resolved.
Show resolved Hide resolved
else it.validate(questionnaireItem, answers, context)
}

return if (validationResults.all { it.isValid }) {
Valid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ import org.hl7.fhir.r4.model.Type

object QuestionnaireResponseValidator {

/** Maps linkId to [ValidationResult]. */
private val linkIdToValidationResultMap = mutableMapOf<String, MutableList<ValidationResult>>()

maimoonak marked this conversation as resolved.
Show resolved Hide resolved
/**
* Validates [QuestionnaireResponse] using the constraints defined in the [Questionnaire].
* - Each item in the [QuestionnaireResponse] must have a corresponding item in the
Expand Down Expand Up @@ -59,24 +56,30 @@ object QuestionnaireResponseValidator {
questionnaireResponse: QuestionnaireResponse,
context: Context
): Map<String, List<ValidationResult>> {
val linkIdToValidationResultMap = mutableMapOf<String, MutableList<ValidationResult>>()

require(
questionnaireResponse.questionnaire == null ||
questionnaire.url == questionnaireResponse.questionnaire
) {
"Mismatching Questionnaire ${questionnaire.url} and QuestionnaireResponse (for Questionnaire ${questionnaireResponse.questionnaire})"
}
validateQuestionnaireResponseItems(questionnaire.item, questionnaireResponse.item, context)
validateQuestionnaireResponseItems(
questionnaire.item,
questionnaireResponse.item,
context,
linkIdToValidationResultMap
)

return linkIdToValidationResultMap
}

private fun validateQuestionnaireResponseItems(
questionnaireItemList: List<Questionnaire.QuestionnaireItemComponent>,
questionnaireResponseItemList: List<QuestionnaireResponse.QuestionnaireResponseItemComponent>,
context: Context
context: Context,
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>
): Map<String, List<ValidationResult>> {

val questionnaireItemListIterator = questionnaireItemList.iterator()
val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator()

Expand All @@ -90,15 +93,21 @@ object QuestionnaireResponseValidator {
questionnaireItem = questionnaireItemListIterator.next()
} while (questionnaireItem!!.linkId != questionnaireResponseItem.linkId)

validateQuestionnaireResponseItem(questionnaireItem, questionnaireResponseItem, context)
validateQuestionnaireResponseItem(
questionnaireItem,
questionnaireResponseItem,
context,
linkIdToValidationResultMap
)
}
return linkIdToValidationResultMap
}

private fun validateQuestionnaireResponseItem(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
context: Context
context: Context,
linkIdToValidationResultMap: MutableMap<String, MutableList<ValidationResult>>
): Map<String, List<ValidationResult>> {

when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
Expand All @@ -109,15 +118,21 @@ object QuestionnaireResponseValidator {
validateQuestionnaireResponseItems(
questionnaireItem.item,
questionnaireResponseItem.item,
context
context,
linkIdToValidationResultMap
)
else -> {
require(questionnaireItem.repeats || questionnaireResponseItem.answer.size <= 1) {
"Multiple answers for non-repeat questionnaire item ${questionnaireItem.linkId}"
}

questionnaireResponseItem.answer.forEach {
validateQuestionnaireResponseItems(questionnaireItem.item, it.item, context)
validateQuestionnaireResponseItems(
questionnaireItem.item,
it.item,
context,
linkIdToValidationResultMap
)
}

linkIdToValidationResultMap[questionnaireItem.linkId] = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture.validation
import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import com.google.android.fhir.datacapture.EXTENSION_HIDDEN_URL
import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal
import org.hl7.fhir.r4.model.Attachment
Expand Down Expand Up @@ -375,6 +376,140 @@ class QuestionnaireResponseValidatorTest {
)
}

@Test
fun `validation passes for required questionnaire item with hidden extension when no value specified`() {
val questionnaire =
Questionnaire().apply {
url = "questionnaire-1"
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("valid-hidden-item"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.INTEGER
)
)
.apply {
this.required = true
addExtension().apply {
url = EXTENSION_HIDDEN_URL
setValue(BooleanType(true))
}
}
)
}
val questionnaireResponse =
QuestionnaireResponse().apply {
this.questionnaire = "questionnaire-1"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("valid-hidden-item"))
)
}

val result =
QuestionnaireResponseValidator.validateQuestionnaireResponse(
questionnaire,
questionnaireResponse,
context
)

assertThat(result.entries.first().key).isEqualTo("valid-hidden-item")
assertThat(result.entries.first().value.first()).isEqualTo(Valid)
}

@Test
fun `validation passes for required questionnaire item with hidden extension when value specified`() {
maimoonak marked this conversation as resolved.
Show resolved Hide resolved
val questionnaire =
Questionnaire().apply {
url = "questionnaire-1"
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("valid-hidden-item"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.INTEGER
)
)
.apply {
required = true
addExtension().apply {
url = EXTENSION_HIDDEN_URL
setValue(BooleanType(true))
}
}
)
}
val questionnaireResponse =
QuestionnaireResponse().apply {
this.questionnaire = "questionnaire-1"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("valid-hidden-item"))
.apply {
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = IntegerType(12)
}
)
}
)
}

val result =
QuestionnaireResponseValidator.validateQuestionnaireResponse(
questionnaire,
questionnaireResponse,
context
)

assertThat(result.entries.first().key).isEqualTo("valid-hidden-item")
assertThat(result.entries.first().value.first()).isEqualTo(Valid)
}

@Test
fun `validation fails for required questionnaire item with hidden extension set to false when no value specified`() {
val questionnaire =
Questionnaire().apply {
url = "questionnaire-1"
addItem(
Questionnaire.QuestionnaireItemComponent(
StringType("valid-hidden-item"),
Enumeration(
Questionnaire.QuestionnaireItemTypeEnumFactory(),
Questionnaire.QuestionnaireItemType.INTEGER
)
)
.apply {
this.required = true
addExtension().apply {
url = EXTENSION_HIDDEN_URL
setValue(BooleanType(false))
}
}
)
}
val questionnaireResponse =
QuestionnaireResponse().apply {
this.questionnaire = "questionnaire-1"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType("valid-hidden-item"))
)
}

val result =
QuestionnaireResponseValidator.validateQuestionnaireResponse(
questionnaire,
questionnaireResponse,
context
)
.entries
.first()

assertThat(result.key).isEqualTo("valid-hidden-item")
assertThat(result.value.first()).isInstanceOf(Invalid::class.java)
assertThat((result.value.first() as Invalid).getSingleStringValidationMessage())
.isEqualTo("Missing answer for required field.")
}

@Test
fun `validate recursively for questionnaire item type GROUP`() {
assertException_validateQuestionnaireResponse_throwsIllegalArgumentException(
Expand Down