Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
-keep public class * extends java.lang.Exception

# --- DTO ---
# Gson 리플렉션으로 역직렬화되는 DTO 클래스의 필드명 보존
# (BaseResponse, ErrorResponse 등이 Gson과 Kotlin Serialization 양쪽에서 사용됨)
# RetrofitV2/RetrofitFlow가 GsonConverterFactory를 사용하므로
# Gson 리플렉션으로 직렬화되는 DTO 필드명 보존 필요
-keepclassmembers class com.runnect.runnect.data.dto.** { <fields>; }

# --- Retrofit + kotlin.Result ---
# Retrofit이 리턴 타입의 제네릭 정보를 리플렉션으로 읽으므로 Signature 유지 필요
# kotlin.Result는 inline class라 R8이 타입 정보를 최적화할 수 있음
# kotlin.Result는 inline class라 R8이 제네릭 타입 정보를 최적화함
# Retrofit이 Call<Result<T>>의 타입 파라미터를 리플렉션으로 읽지 못해 CallAdapter 생성 실패
# https://github.com/square/retrofit/issues/3880
-keep class kotlin.Result { *; }
-keepattributes Signature
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.runnect.runnect.data.network.calladapter

import com.google.gson.Gson
import com.runnect.runnect.data.dto.response.base.ErrorResponse
import com.runnect.runnect.domain.common.RunnectException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
Expand All @@ -11,7 +13,7 @@ import okio.Timeout

class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> {

private val gson = Gson()
private val json = Json { ignoreUnknownKeys = true }

override fun execute(): Response<Result<T>> {
throw UnsupportedOperationException("ResultCall doesn't support execute")
Expand Down Expand Up @@ -46,23 +48,17 @@ class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> {
}

private fun parseErrorResponse(response: Response<*>): RunnectException {
val errorJson = response.errorBody()?.string()
val errorString = response.errorBody()?.string()

return runCatching {
val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java)
val message = errorBody?.run {
message ?: error ?: ERROR_MSG_COMMON
}

RunnectException(
code = errorBody.status,
message = message
)
val jsonObject = json.parseToJsonElement(errorString.orEmpty()).jsonObject
val status = jsonObject["status"]?.jsonPrimitive?.int ?: response.code()
val message = jsonObject["message"]?.jsonPrimitive?.content
?: jsonObject["error"]?.jsonPrimitive?.content
?: ERROR_MSG_COMMON
RunnectException(code = status, message = message)
}.getOrElse {
RunnectException(
code = response.code(),
message = ERROR_MSG_COMMON
)
RunnectException(code = response.code(), message = ERROR_MSG_COMMON)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.runnect.runnect.data.network.calladapter.flow

import com.google.gson.Gson
import com.runnect.runnect.data.dto.response.base.ErrorResponse
import com.runnect.runnect.domain.common.RunnectException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
Expand All @@ -18,7 +19,7 @@ class FlowCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Flow<Result<T>>> {

private val gson = Gson()
private val json = Json { ignoreUnknownKeys = true }
override fun responseType() = responseType

// Retrofit의 Call을 Result<>로 변환
Expand Down Expand Up @@ -58,15 +59,18 @@ class FlowCallAdapter<T>(
} ?: Result.failure(nullBodyException)
}

// Response에서 오류를 파싱하여 RunnectException 객체를 생성
private fun parseErrorResponse(response: Response<*>): RunnectException {
val errorBodyString = response.errorBody()?.string()
val errorResponse = errorBodyString?.let {
gson.fromJson(it, ErrorResponse::class.java)
}

val errorMessage = errorResponse?.message ?: errorResponse?.error ?: ERROR_MSG_COMMON
return RunnectException(response.code(), errorMessage)
return runCatching {
val jsonObject = json.parseToJsonElement(errorBodyString.orEmpty()).jsonObject
val message = jsonObject["message"]?.jsonPrimitive?.content
?: jsonObject["error"]?.jsonPrimitive?.content
?: ERROR_MSG_COMMON
RunnectException(response.code(), message)
}.getOrElse {
RunnectException(response.code(), ERROR_MSG_COMMON)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.runnect.runnect.data.network.interceptor

import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonSyntaxException
import com.runnect.runnect.data.dto.response.base.BaseResponse
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Response
Expand All @@ -18,14 +15,14 @@ import timber.log.Timber
*/
class ResponseInterceptor : Interceptor {

private val gson = Gson()
private val json = Json { ignoreUnknownKeys = true }

override fun intercept(chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed(chain.request())
if (!originalResponse.isSuccessful) return originalResponse

val bodyString = originalResponse.peekBody(Long.MAX_VALUE).string()
val newResponseBodyString = jsonToBaseResponse(bodyString)?.let {
val newResponseBodyString = extractData(bodyString)?.let {
it.toResponseBody("application/json".toMediaTypeOrNull())
} ?: return originalResponse

Expand All @@ -42,26 +39,18 @@ class ResponseInterceptor : Interceptor {
}
}

private fun jsonToBaseResponse(body: String): String? {
private fun extractData(body: String): String? {
return try {
val jsonElement: JsonElement = gson.fromJson(body, JsonElement::class.java)
if (!isBaseResponse(jsonElement.asJsonObject)) {
return null
}

val baseResponse = gson.fromJson(body, BaseResponse::class.java)
gson.toJson(baseResponse.data)
} catch (e: JsonSyntaxException) {
null // JSON 구문 분석 오류 발생 시 원래 형식을 반환
} catch (e: JsonParseException) {
null // JSON 파싱 오류 발생 시 원래 형식을 반환
val jsonObject = json.parseToJsonElement(body).jsonObject
if (!isBaseResponse(jsonObject)) return null
jsonObject["data"].toString()
} catch (e: Exception) {
null // 기타 예외 발생 시 원래 형식을 반환
null
}
}

private fun isBaseResponse(jsonObject: JsonObject): Boolean {
val requiredFields = listOf("status", "success", "message", "data")
return requiredFields.all { jsonObject.has(it) }
return requiredFields.all { it in jsonObject }
}
}