Skip to content

Commit

Permalink
#187 의약품 허가 상세 정보 응답시 String으로 먼저 받고 오류 처리한 뒤 MedicineDetailInfoRespo…
Browse files Browse the repository at this point in the history
…nse로 파싱하는 방식으로 수정
  • Loading branch information
pknujsp committed Jul 25, 2023
1 parent 3dc1e40 commit 4e77dc3
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -571,11 +571,41 @@ val ubDocData = """
""".trimIndent()

val eeDocData = """
<DOC title="효능효과" type="EE">
<DOC title="효능효과" type="EE">
<SECTION title="">
<ARTICLE title="">
<PARAGRAPH tagName="p" textIndent="0" marginLeft="0">
<![CDATA[체질량지수(BMI) 30kg/m<sup>2</sup> 이상의 비만환자 또는 다른 위험인자(예. 제2형 당뇨, 이상지질혈증, 고혈압)가 있는 체질량지수(BMI) 27kg/m<sup>2</sup> 이상 30kg/m<sup>2</sup> 미만인 과체중 환자의 체중조절을 위한 식이 및 운동요법의 보조요법]]>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[○ 유효균종]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[피페라실린에 감수성인 녹농균, 프로테우스, 시트로박터, 클레브시엘라, 엔테로박터, 세라티아, 대장균, 인플루엔자균, 임균, 박테로이드, 클로스트리듐, 펩토구균, 펩토연쇄구균, 연쇄구균, 폐렴연쇄구균, 포도구균, 장내구균]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[○ 적응증]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 패혈증]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 급만성기관지염, 농흉, 폐렴, 폐농양, 기관지확장증, 만성호흡기질환의 2차감염]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 신우신염, 방광염, 임균성요도염]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 자궁내막염, 골반사강염]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 복막염, 복강내농양, 골반농양, 난관염]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 피부 및 연조직감염증]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 담관염]]>
</PARAGRAPH>
<PARAGRAPH tagName="p" textIndent="" marginLeft="">
<![CDATA[- 호기성균 및 혐기성균의 혼합감염증]]>
</PARAGRAPH>
</ARTICLE>
</SECTION>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.full.starProjectedType

abstract class BaseNavArgs(
val className: String) : NavArgs {
val className: String,
) : NavArgs {
fun toBundle(): Bundle {
return bundleOf(*toMap().toList().toTypedArray())
}
Expand Down Expand Up @@ -137,6 +138,8 @@ abstract class BaseNavArgs(

// 속성 값이 NULL이면 빈 값을 넣어준다.
fun toMap(): Map<String, Any> = this::class.memberProperties.associate { property ->
property.name to (property.getter.call(this) ?: emptyValue(property.returnType))
property.name to (property.getter.call(this).run {
if (this is String) replace("%", "%25") else this
} ?: emptyValue(property.returnType))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,48 +69,48 @@ data class MedicineDetailInfoResponse(
*/
@Serializable
data class Item(
@SerialName("ATC_CODE") val atcCode: String?,
@SerialName("BAR_CODE") val barCode: String?,
@SerialName("BIZRNO") val businessRegistrationNumber: String?,
@SerialName("CANCEL_DATE") val cancelDate: String?,
@SerialName("CANCEL_NAME") val cancelName: String?,
@SerialName("CHANGE_DATE") val changeDate: String,
@SerialName("CHART") val chart: String?,
@SerialName("CNSGN_MANUF") val consignmentManufacturer: String?,
@SerialName("DOC_TEXT") val docText: String?,
@SerialName("EDI_CODE") val ediCode: String?,
@SerialName("EE_DOC_DATA") val eeDocData: String,
@SerialName("EE_DOC_ID") val eeDocId: String?,
@SerialName("ENTP_ENG_NAME") val entpEnglishName: String?,
@SerialName("ENTP_NAME") val entpName: String,
@SerialName("ENTP_NO") val entpNumber: String?,
@SerialName("ETC_OTC_CODE") val etcOtcCode: String,
@SerialName("GBN_NAME") val gbnName: String?,
@SerialName("INDUTY_TYPE") val industryType: String?,
@SerialName("INGR_NAME") val ingredientName: String,
@SerialName("INSERT_FILE") val insertFileUrl: String?,
@SerialName("ITEM_ENG_NAME") val itemEnglishName: String,
@SerialName("ITEM_NAME") val itemName: String,
@SerialName("ITEM_PERMIT_DATE") val itemPermitDate: String,
@SerialName("ITEM_SEQ") val itemSequence: String,
@SerialName("MAIN_INGR_ENG") val mainIngredientEnglish: String?,
@SerialName("MAIN_ITEM_INGR") val mainItemIngredient: String,
@SerialName("MAKE_MATERIAL_FLAG") val makeMaterialFlag: String?,
@SerialName("MATERIAL_NAME") val materialName: String?,
@SerialName("NARCOTIC_KIND_CODE") val narcoticKindCode: String?,
@SerialName("NB_DOC_DATA") val nbDocData: String,
@SerialName("NB_DOC_ID") val nbDocId: String?,
@SerialName("NEWDRUG_CLASS_NAME") val newDrugClassName: String?,
@SerialName("PACK_UNIT") val packUnit: String?,
@SerialName("PERMIT_KIND_NAME") val permitKindName: String?,
@SerialName("PN_DOC_DATA") val pnDocData: String?,
@SerialName("REEXAM_DATE") val reexamDate: String?,
@SerialName("REEXAM_TARGET") val reexamTarget: String?,
@SerialName("STORAGE_METHOD") val storageMethod: String?,
@SerialName("TOTAL_CONTENT") val totalContent: String?,
@SerialName("UD_DOC_DATA") val udDocData: String,
@SerialName("UD_DOC_ID") val uDDOCID: String?,
@SerialName("VALID_TERM") val validTerm: String?,
@SerialName("ATC_CODE") val atcCode: String = "",
@SerialName("BAR_CODE") val barCode: String = "",
@SerialName("BIZRNO") val businessRegistrationNumber: String = "",
@SerialName("CANCEL_DATE") val cancelDate: String = "",
@SerialName("CANCEL_NAME") val cancelName: String = "",
@SerialName("CHANGE_DATE") val changeDate: String = "",
@SerialName("CHART") val chart: String = "",
@SerialName("CNSGN_MANUF") val consignmentManufacturer: String = "",
@SerialName("DOC_TEXT") val docText: String = "",
@SerialName("EDI_CODE") val ediCode: String = "",
@SerialName("EE_DOC_DATA") val eeDocData: String = "",
@SerialName("EE_DOC_ID") val eeDocId: String = "",
@SerialName("ENTP_ENG_NAME") val entpEnglishName: String = "",
@SerialName("ENTP_NAME") val entpName: String = "",
@SerialName("ENTP_NO") val entpNumber: String = "",
@SerialName("ETC_OTC_CODE") val etcOtcCode: String = "",
@SerialName("GBN_NAME") val gbnName: String = "",
@SerialName("INDUTY_TYPE") val industryType: String = "",
@SerialName("INGR_NAME") val ingredientName: String = "",
@SerialName("INSERT_FILE") val insertFileUrl: String = "",
@SerialName("ITEM_ENG_NAME") val itemEnglishName: String = "",
@SerialName("ITEM_NAME") val itemName: String = "",
@SerialName("ITEM_PERMIT_DATE") val itemPermitDate: String = "",
@SerialName("ITEM_SEQ") val itemSequence: String = "",
@SerialName("MAIN_INGR_ENG") val mainIngredientEnglish: String = "",
@SerialName("MAIN_ITEM_INGR") val mainItemIngredient: String = "",
@SerialName("MAKE_MATERIAL_FLAG") val makeMaterialFlag: String = "",
@SerialName("MATERIAL_NAME") val materialName: String = "",
@SerialName("NARCOTIC_KIND_CODE") val narcoticKindCode: String = "",
@SerialName("NB_DOC_DATA") val nbDocData: String = "",
@SerialName("NB_DOC_ID") val nbDocId: String = "",
@SerialName("NEWDRUG_CLASS_NAME") val newDrugClassName: String = "",
@SerialName("PACK_UNIT") val packUnit: String = "",
@SerialName("PERMIT_KIND_NAME") val permitKindName: String = "",
@SerialName("PN_DOC_DATA") val pnDocData: String = "",
@SerialName("REEXAM_DATE") val reexamDate: String = "",
@SerialName("REEXAM_TARGET") val reexamTarget: String = "",
@SerialName("STORAGE_METHOD") val storageMethod: String = "",
@SerialName("TOTAL_CONTENT") val totalContent: String = "",
@SerialName("UD_DOC_DATA") val udDocData: String = "",
@SerialName("UD_DOC_ID") val uDDOCID: String = "",
@SerialName("VALID_TERM") val validTerm: String = "",
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.android.mediproject.core.network

import com.android.mediproject.core.model.DataGoKrBaseResponse
import com.android.mediproject.core.model.toResult
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import retrofit2.Response

/**
Expand All @@ -9,7 +13,7 @@ import retrofit2.Response
*
* HTTP응답이 성공이면, 응답 객체(T)를 반환하고, 실패이면 실패 이유를 반환한다.
*/
inline fun <reified T : Any> Response<T>.onResponse(): Result<T> {
internal inline fun <reified T : Any> Response<T>.onResponse(): Result<T> {
return if (isSuccessful) {
body()?.let { body ->
Result.success(body)
Expand All @@ -18,3 +22,36 @@ inline fun <reified T : Any> Response<T>.onResponse(): Result<T> {
Result.failure(errorBody()?.string()?.let { Throwable(it) } ?: Throwable("Response Error"))
}
}

private val json = Json { coerceInputValues = true }

private inline fun <reified T : Any> String.parse(): Result<T> = try {
Result.success(json.decodeFromString(this))
} catch (e: Exception) {
Result.failure(e)
}

internal inline fun <reified T : DataGoKrBaseResponse> Response<String>.onStringResponse(): Result<PairResponse<T, String>> {
return if (isSuccessful) {
body()?.parse<T>()?.fold(
onSuccess = { final ->
final.toResult().fold(
onSuccess = {
Result.success(PairResponse(it, body()!!))
},
onFailure = {
Result.failure(it)
},
)
},
onFailure = { Result.failure(it) },
) ?: Result.failure(Throwable("Response Body is Null"))
} else {
Result.failure(errorBody()?.string()?.let { Throwable(it) } ?: Throwable("Response Error"))
}
}

internal data class PairResponse<T : Any, E : Any>(
val first: T,
val second: E,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,53 @@ import com.android.mediproject.core.model.medicine.medicinedetailinfo.cache.Medi
import com.android.mediproject.core.model.toResult
import com.android.mediproject.core.network.datasource.image.GoogleSearchDataSource
import com.android.mediproject.core.network.module.DataGoKrNetworkApi
import com.android.mediproject.core.network.module.safetyEncode
import com.android.mediproject.core.network.onResponse
import kotlinx.coroutines.Dispatchers
import com.android.mediproject.core.network.onStringResponse
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.lang.ref.WeakReference
import javax.inject.Inject

class MedicineApprovalDataSourceImpl @Inject constructor(
private val dataGoKrNetworkApi: DataGoKrNetworkApi,
private val dataGoKrNetworkApiWithString: DataGoKrNetworkApi,
private val dataGoKrNetworkApiWithJson: DataGoKrNetworkApi,
private val medicineDataCacheManager: MedicineDataCacheManager,
private val googleSearchDataSource: GoogleSearchDataSource,
private val defaultDispatcher: CoroutineDispatcher,
) : MedicineApprovalDataSource {

override suspend fun getMedicineApprovalList(
itemName: String?, entpName: String?, medicationType: String?, pageNo: Int,
): Result<MedicineApprovalListResponse> =
dataGoKrNetworkApi.getApprovalList(itemName = itemName, entpName = entpName, pageNo = pageNo, medicationType = medicationType).onResponse()
.fold(
onSuccess = { response ->
response.toResult().fold(
onSuccess = {
// 이미지가 없는 경우 구글 검색을 통해 이미지를 가져온다.
loadMedicineImageUrl(response)
Result.success(response)
},
onFailure = {
Result.failure(it)
},
)
},
onFailure = {
Result.failure(it)
},
)
): Result<MedicineApprovalListResponse> = dataGoKrNetworkApiWithJson.getApprovalList(
itemName = itemName?.safetyEncode(), entpName = entpName?.safetyEncode(), pageNo = pageNo,
medicationType = medicationType,
).onResponse().fold(
onSuccess = { response ->
response.toResult().fold(
onSuccess = {
loadMedicineImageUrl(it)
Result.success(response)
},
onFailure = {
Result.failure(it)
},
)
},
onFailure = {
Result.failure(it)
},
)

override fun getMedicineDetailInfo(itemName: String): Flow<Result<MedicineDetailInfoResponse>> = channelFlow {
dataGoKrNetworkApi.getMedicineDetailInfo(itemName = itemName).let { response ->
response.onResponse().fold(
dataGoKrNetworkApiWithString.getMedicineDetailInfo(itemName = itemName.safetyEncode()).let { response ->
response.onStringResponse<MedicineDetailInfoResponse>().fold(
onSuccess = { entity ->
entity.toResult().fold(
onSuccess = {
response.body()?.run { cache(this) }
Result.success(entity)
},
onFailure = {
Result.failure(it)
},
)
cache(entity.first, entity.second)
Result.success(entity.first)
},
onFailure = {
Result.failure(it)
Expand All @@ -71,18 +66,11 @@ class MedicineApprovalDataSourceImpl @Inject constructor(

override fun getMedicineDetailInfoByItemSeq(itemSeqs: List<String>) = channelFlow {
val responses = itemSeqs.map { itemSeq ->
dataGoKrNetworkApi.getMedicineDetailInfo(itemSeq = itemSeq).let { response ->
response.onResponse().fold(
dataGoKrNetworkApiWithJson.getMedicineDetailInfo(itemSeq = itemSeq).let { response ->
response.onStringResponse<MedicineDetailInfoResponse>().fold(
onSuccess = { entity ->
entity.toResult().fold(
onSuccess = {
response.body()?.run { cache(this) }
Result.success(entity)
},
onFailure = {
Result.failure(it)
},
)
cache(entity.first, entity.second)
Result.success(entity.first)
},
onFailure = {
Result.failure(it)
Expand All @@ -91,29 +79,27 @@ class MedicineApprovalDataSourceImpl @Inject constructor(
}
}

val results = responses.let {
val failed = it.any { result -> result.isFailure }
if (failed) Result.failure(Throwable("약품 상세 정보 조회에 실패했습니다."))
else Result.success(it.map { result -> result.getOrNull()!! })
val result = responses.filter { it.isFailure }.run {
if (isEmpty()) Result.success(responses.map { it.getOrNull()!! })
else Result.failure(first().exceptionOrNull()!!)
}

trySend(results)
send(result)
}

private fun cache(response: MedicineDetailInfoResponse) {
with(response.body.items[0]) {
medicineDataCacheManager.updateDetail(
MedicineCacheEntity(
itemSequence = itemSequence,
json = WeakReference(Json.encodeToString(response.body)).get()!!,
changeDate = changeDate,
),
)
}
private fun cache(response: MedicineDetailInfoResponse, string: String) {
val item = response.body.items.first()
medicineDataCacheManager.updateDetail(
MedicineCacheEntity(
itemSequence = item.itemSequence,
json = WeakReference(string).get()!!,
changeDate = item.changeDate,
),
)
}

private suspend fun loadMedicineImageUrl(medicineApprovalListResponse: MedicineApprovalListResponse) {
return withContext(Dispatchers.Default) {
return withContext(defaultDispatcher) {
val items = mutableListOf<Pair<Int, String>>()
medicineApprovalListResponse.body.items.forEachIndexed { index, item ->
if (item.bigPrdtImgUrl.isEmpty()) items.add(index to item.itemName)
Expand Down
Loading

0 comments on commit 4e77dc3

Please sign in to comment.