Skip to content

Commit 3d36461

Browse files
committed
Validate extracted fhir resources while in debug
1 parent 0ec96d7 commit 3d36461

File tree

7 files changed

+96
-4
lines changed

7 files changed

+96
-4
lines changed

android/engine/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ dependencies {
157157
exclude(group = "ca.uhn.hapi.fhir")
158158
}
159159

160+
implementation(libs.hapi.fhir.validation) { exclude(module = "commons-logging") }
161+
160162
// Shared dependencies
161163
api(libs.glide)
162164
api(libs.knowledger)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2021-2023 Ona Systems, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.smartregister.fhircore.engine.di
18+
19+
import ca.uhn.fhir.context.FhirContext
20+
import ca.uhn.fhir.validation.FhirValidator
21+
import dagger.Module
22+
import dagger.Provides
23+
import dagger.hilt.InstallIn
24+
import dagger.hilt.components.SingletonComponent
25+
import javax.inject.Singleton
26+
import kotlin.coroutines.coroutineContext
27+
import kotlinx.coroutines.withContext
28+
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator
29+
import org.hl7.fhir.r4.model.Resource
30+
import org.smartregister.fhircore.engine.BuildConfig
31+
import org.smartregister.fhircore.engine.util.extension.encodeResourceToString
32+
import timber.log.Timber
33+
34+
@Module
35+
@InstallIn(SingletonComponent::class)
36+
class FhirValidatorModule {
37+
38+
@Provides
39+
@Singleton
40+
fun provideFhirValidator(): FhirValidator {
41+
val fhirContext = FhirContext.forR4()
42+
val validatorModule = FhirInstanceValidator(fhirContext)
43+
return fhirContext.newValidator().apply { registerValidatorModule(validatorModule) }
44+
}
45+
}
46+
47+
fun doRunValidation() = BuildConfig.DEBUG
48+
49+
suspend fun FhirValidator.checkResourceValid(resource: Resource) {
50+
if (!doRunValidation()) return
51+
withContext(coroutineContext) {
52+
val result = this@checkResourceValid.validateWithResult(resource)
53+
if (!result.isSuccessful) {
54+
Timber.e(resource.encodeResourceToString())
55+
throw IllegalStateException(result.messages.joinToString { it.message })
56+
}
57+
}
58+
}

android/engine/src/main/java/org/smartregister/fhircore/engine/task/FhirCarePlanGenerator.kt

+16-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ package org.smartregister.fhircore.engine.task
1919
import androidx.annotation.VisibleForTesting
2020
import ca.uhn.fhir.context.FhirContext
2121
import ca.uhn.fhir.util.TerserUtil
22+
import ca.uhn.fhir.validation.FhirValidator
2223
import com.google.android.fhir.FhirEngine
2324
import com.google.android.fhir.get
2425
import com.google.android.fhir.logicalId
2526
import com.google.android.fhir.search.search
2627
import java.util.Date
2728
import javax.inject.Inject
29+
import javax.inject.Provider
2830
import javax.inject.Singleton
2931
import org.hl7.fhir.r4.model.ActivityDefinition
3032
import org.hl7.fhir.r4.model.Base
@@ -54,6 +56,7 @@ import org.hl7.fhir.r4.utils.StructureMapUtilities
5456
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
5557
import org.smartregister.fhircore.engine.configuration.event.EventType
5658
import org.smartregister.fhircore.engine.data.local.DefaultRepository
59+
import org.smartregister.fhircore.engine.di.checkResourceValid
5760
import org.smartregister.fhircore.engine.util.extension.addResourceParameter
5861
import org.smartregister.fhircore.engine.util.extension.asReference
5962
import org.smartregister.fhircore.engine.util.extension.encodeResourceToString
@@ -75,6 +78,7 @@ constructor(
7578
val transformSupportServices: TransformSupportServices,
7679
val defaultRepository: DefaultRepository,
7780
val fhirResourceUtil: FhirResourceUtil,
81+
val fhirValidatorProvider: Provider<FhirValidator>,
7882
val workflowCarePlanGenerator: WorkflowCarePlanGenerator,
7983
) {
8084
private val structureMapUtilities by lazy {
@@ -208,9 +212,15 @@ constructor(
208212
carePlan.contained.clear()
209213

210214
// Save CarePlan only if it has activity, otherwise just save contained/dependent resources
211-
if (output.hasActivity()) defaultRepository.create(true, carePlan)
215+
if (output.hasActivity()) {
216+
fhirValidatorProvider.get().checkResourceValid(carePlan)
217+
defaultRepository.create(true, carePlan)
218+
}
212219

213-
dependents.forEach { defaultRepository.create(true, it) }
220+
dependents.forEach {
221+
fhirValidatorProvider.get().checkResourceValid(it)
222+
defaultRepository.create(true, it)
223+
}
214224

215225
if (carePlan.status == CarePlan.CarePlanStatus.COMPLETED) {
216226
carePlan.activity
@@ -238,7 +248,10 @@ constructor(
238248
if (reason != null) this.statusReason = CodeableConcept().apply { text = reason }
239249
}
240250
?.updateDependentTaskDueDate(defaultRepository)
241-
?.run { defaultRepository.addOrUpdate(addMandatoryTags = true, resource = this) }
251+
?.run {
252+
fhirValidatorProvider.get().checkResourceValid(this)
253+
defaultRepository.addOrUpdate(addMandatoryTags = true, resource = this)
254+
}
242255
}
243256

244257
suspend fun cancelTaskByTaskId(id: String, reason: String) {

android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import ca.uhn.fhir.context.FhirContext
2121
import ca.uhn.fhir.context.FhirVersionEnum
2222
import ca.uhn.fhir.parser.IParser
2323
import ca.uhn.fhir.rest.gclient.ReferenceClientParam
24+
import ca.uhn.fhir.validation.FhirValidator
2425
import com.google.android.fhir.FhirEngine
2526
import com.google.android.fhir.SearchResult
2627
import com.google.android.fhir.get
@@ -45,6 +46,7 @@ import java.util.Calendar
4546
import java.util.Date
4647
import java.util.UUID
4748
import javax.inject.Inject
49+
import javax.inject.Provider
4850
import kotlin.time.Duration.Companion.seconds
4951
import kotlinx.coroutines.Dispatchers
5052
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -128,6 +130,8 @@ class FhirCarePlanGeneratorTest : RobolectricTest() {
128130

129131
@Inject lateinit var workflowCarePlanGenerator: WorkflowCarePlanGenerator
130132

133+
@Inject lateinit var fhirValidatorProvider: Provider<FhirValidator>
134+
131135
@Inject lateinit var fhirPathEngine: FHIRPathEngine
132136

133137
@Inject lateinit var fhirEngine: FhirEngine
@@ -166,6 +170,7 @@ class FhirCarePlanGeneratorTest : RobolectricTest() {
166170
fhirPathEngine = fhirPathEngine,
167171
defaultRepository = defaultRepository,
168172
fhirResourceUtil = fhirResourceUtil,
173+
fhirValidatorProvider = fhirValidatorProvider,
169174
workflowCarePlanGenerator = workflowCarePlanGenerator,
170175
)
171176

android/gradle/libs.versions.toml

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ work-runtime-ktx = "2.8.1"
9898
work-testing = "2.8.1"
9999
workflow = "0.1.0-alpha02-preview9.1-SNAPSHOT"
100100
xercesImpl = "2.12.2"
101+
hapi-fhir = "6.0.1"
101102

102103
[libraries]
103104
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist-flowlayout" }
@@ -205,5 +206,6 @@ work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version
205206
work-testing = { group = "androidx.work", name = "work-testing", version.ref = "work-testing" }
206207
workflow = { group = "org.smartregister", name = "workflow", version.ref = "workflow" }
207208
xercesImpl = { group = "xerces", name = "xercesImpl", version.ref = "xercesImpl" }
209+
hapi-fhir-validation = {group = "ca.uhn.hapi.fhir", name="hapi-fhir-validation", version.ref = "hapi-fhir"}
208210

209211
[plugins]

android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData
2222
import androidx.lifecycle.MutableLiveData
2323
import androidx.lifecycle.ViewModel
2424
import androidx.lifecycle.viewModelScope
25+
import ca.uhn.fhir.validation.FhirValidator
2526
import com.google.android.fhir.datacapture.mapping.ResourceMapper
2627
import com.google.android.fhir.datacapture.mapping.StructureMapExtractionContext
2728
import com.google.android.fhir.datacapture.validation.NotValidated
@@ -35,6 +36,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
3536
import java.util.Date
3637
import java.util.UUID
3738
import javax.inject.Inject
39+
import javax.inject.Provider
3840
import kotlinx.coroutines.SupervisorJob
3941
import kotlinx.coroutines.launch
4042
import kotlinx.coroutines.withContext
@@ -45,7 +47,6 @@ import org.hl7.fhir.r4.model.Group
4547
import org.hl7.fhir.r4.model.IdType
4648
import org.hl7.fhir.r4.model.ListResource
4749
import org.hl7.fhir.r4.model.ListResource.ListEntryComponent
48-
import org.hl7.fhir.r4.model.Patient
4950
import org.hl7.fhir.r4.model.Questionnaire
5051
import org.hl7.fhir.r4.model.QuestionnaireResponse
5152
import org.hl7.fhir.r4.model.RelatedPerson
@@ -55,6 +56,7 @@ import org.hl7.fhir.r4.model.StringType
5556
import org.smartregister.fhircore.engine.configuration.GroupResourceConfig
5657
import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig
5758
import org.smartregister.fhircore.engine.data.local.DefaultRepository
59+
import org.smartregister.fhircore.engine.di.checkResourceValid
5860
import org.smartregister.fhircore.engine.domain.model.ActionParameter
5961
import org.smartregister.fhircore.engine.domain.model.ActionParameterType
6062
import org.smartregister.fhircore.engine.rulesengine.ResourceDataRulesExecutor
@@ -93,6 +95,7 @@ constructor(
9395
val transformSupportServices: TransformSupportServices,
9496
val sharedPreferencesHelper: SharedPreferencesHelper,
9597
val fhirOperator: FhirOperator,
98+
val fhirValidatorProvider: Provider<FhirValidator>,
9699
val fhirPathDataExtractor: FhirPathDataExtractor,
97100
) : ViewModel() {
98101

@@ -346,6 +349,7 @@ constructor(
346349
}
347350
}
348351

352+
fhirValidatorProvider.get().checkResourceValid(this)
349353
defaultRepository.addOrUpdate(true, resource = this)
350354

351355
addMemberToConfiguredGroup(this, questionnaireConfig.groupResource)
@@ -368,6 +372,7 @@ constructor(
368372
!currentQuestionnaireResponse.subject.reference.isNullOrEmpty() &&
369373
questionnaireConfig.saveQuestionnaireResponse
370374
) {
375+
fhirValidatorProvider.get().checkResourceValid(currentQuestionnaireResponse)
371376
defaultRepository.addOrUpdate(resource = currentQuestionnaireResponse)
372377
}
373378
}
@@ -514,6 +519,7 @@ constructor(
514519
}
515520
if (questionnaireHasAnswer) {
516521
questionnaireResponse.status = QuestionnaireResponse.QuestionnaireResponseStatus.INPROGRESS
522+
// fhirValidatorProvider.get().validateResource(questionnaireResponse)
517523
defaultRepository.addOrUpdate(addMandatoryTags = true, resource = questionnaireResponse)
518524
}
519525
}
@@ -669,6 +675,7 @@ constructor(
669675
* time anything linked to it in order to change the `_lastUpdated` timestamp. This helps us
670676
* with order the last updated group (household) on the top of the register.
671677
*/
678+
fhirValidatorProvider.get().checkResourceValid(group)
672679
defaultRepository.addOrUpdate(resource = group)
673680
}
674681

android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.smartregister.fhircore.quest.ui.questionnaire
1818

1919
import android.app.Application
2020
import androidx.test.core.app.ApplicationProvider
21+
import ca.uhn.fhir.validation.FhirValidator
2122
import com.google.android.fhir.FhirEngine
2223
import com.google.android.fhir.datacapture.mapping.ResourceMapper
2324
import com.google.android.fhir.db.ResourceNotFoundException
@@ -40,6 +41,7 @@ import io.mockk.verify
4041
import java.util.Date
4142
import java.util.UUID
4243
import javax.inject.Inject
44+
import javax.inject.Provider
4345
import kotlin.time.Duration.Companion.seconds
4446
import kotlinx.coroutines.ExperimentalCoroutinesApi
4547
import kotlinx.coroutines.runBlocking
@@ -108,6 +110,8 @@ class QuestionnaireViewModelTest : RobolectricTest() {
108110

109111
@Inject lateinit var sharedPreferencesHelper: SharedPreferencesHelper
110112

113+
@Inject lateinit var fhirValidatorProvider: Provider<FhirValidator>
114+
111115
@Inject lateinit var configService: ConfigService
112116

113117
@Inject lateinit var resourceDataRulesExecutor: ResourceDataRulesExecutor
@@ -180,6 +184,7 @@ class QuestionnaireViewModelTest : RobolectricTest() {
180184
fhirCarePlanGenerator = fhirCarePlanGenerator,
181185
resourceDataRulesExecutor = resourceDataRulesExecutor,
182186
fhirPathDataExtractor = fhirPathDataExtractor,
187+
fhirValidatorProvider = fhirValidatorProvider,
183188
fhirOperator = fhirOperator,
184189
),
185190
)

0 commit comments

Comments
 (0)