-
Notifications
You must be signed in to change notification settings - Fork 0
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
YW2-237 feat: 공통 Response 및 ControllerAdvice 정의 #5
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.dangle.api.common.error | ||
|
||
import com.dangle.api.common.phase.ActiveProfilesResolver | ||
import com.dangle.api.common.response.ApiResponse | ||
import com.dangle.common.DangleErrorCode | ||
import com.dangle.common.DangleException | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.http.HttpStatus | ||
import org.springframework.http.HttpStatusCode | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.web.bind.annotation.ExceptionHandler | ||
import org.springframework.web.bind.annotation.RestControllerAdvice | ||
|
||
@RestControllerAdvice | ||
class DangleApiExceptionControllerAdvice( | ||
private val activeProfilesResolver: ActiveProfilesResolver | ||
) { | ||
|
||
private val logger = LoggerFactory.getLogger(this::class.java) | ||
|
||
@ExceptionHandler(DangleException::class) | ||
fun handleDangleException(e: DangleException): ResponseEntity<ApiResponse<Any?>>{ | ||
return ResponseEntity( | ||
ApiResponse.error( | ||
errorCode = e.errorCode, | ||
error = e.toError(), | ||
debug = e.toDebug() | ||
), | ||
e.errorCode.httpStatusCode() | ||
) | ||
} | ||
|
||
// 별도로 Exception 을 처리할 것들은 이앞에서 다시 처리할 것 | ||
@ExceptionHandler(Throwable::class) | ||
fun handleUnKnownException(e: Throwable): ResponseEntity<ApiResponse<Any?>>{ | ||
logger.error(e.stackTraceToString()) | ||
return handleDangleException( | ||
DangleException( | ||
errorCode = DangleErrorCode.INTERNAL_SERVER_ERROR, | ||
debug = e.message, | ||
throwable = e | ||
) | ||
) | ||
} | ||
|
||
private fun DangleErrorCode.httpStatusCode(): HttpStatusCode{ | ||
return when (value) { | ||
in 4000 until 4100 -> HttpStatus.BAD_REQUEST | ||
in 4100 until 4200 -> HttpStatus.NOT_FOUND | ||
in 5000 until 5100 -> HttpStatus.INTERNAL_SERVER_ERROR | ||
else -> HttpStatus.INTERNAL_SERVER_ERROR | ||
} | ||
} | ||
|
||
|
||
private fun DangleException.toError(): String { | ||
return this.errorCode.name | ||
} | ||
private fun DangleException.toDebug(): String?{ | ||
if(activeProfilesResolver.isPrd()){ | ||
return null | ||
} | ||
return this.stackTraceToString() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 Prod 환경에서는 스택트레이스를 노출하지 않는군요,, |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.dangle.api.common.error | ||
|
||
import com.dangle.common.DangleErrorCode | ||
|
||
data class ErrorResponse( | ||
val code: Int, | ||
val message: String, | ||
val data: String? = null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ErrorResponse의 data 부는 어떤 값들이 들어가게 될까요? 특별한 값이 아니라면, DangleExeption만으로도 표현이 가능할 것 같아서요! data class Error<T>(
val code: String,
val message: String,
val debug: String? = null,
) : ApiResponse<T> {
override val result = ResponseType.ERROR
}
ApiResponse.Error(
error = dangleException.errorCode.value,
message = dangleException.errorCode.message,
debug = dangleException.debug,
) |
||
){ | ||
constructor(errorCode: DangleErrorCode,data:String?):this( | ||
code = errorCode.value, | ||
message = errorCode.message, | ||
data = data | ||
|
||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.dangle.api.common.phase | ||
|
||
import org.springframework.core.env.Environment | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class ActiveProfilesResolver( | ||
env: Environment | ||
){ | ||
|
||
private val activeProfilePhases = ActiveProfilePhase.values().map { it.phase }.toSet() | ||
|
||
private val currentProfiles = env.activeProfiles | ||
.filter { it in activeProfilePhases } | ||
.associateBy{ it } | ||
.ifEmpty { mapOf(ActiveProfilePhase.LOCAL.phase to ActiveProfilePhase.LOCAL) } | ||
fun isPrd() : Boolean{ | ||
return currentProfiles[ActiveProfilePhase.PRD.phase] != null | ||
} | ||
|
||
fun isDev() : Boolean{ | ||
return currentProfiles[ActiveProfilePhase.DEV.phase] != null | ||
} | ||
|
||
fun isLocal(): Boolean{ | ||
return currentProfiles[ActiveProfilePhase.LOCAL.phase] != null | ||
} | ||
private enum class ActiveProfilePhase(val phase: String) { | ||
LOCAL("local"), | ||
DEV("dev"), | ||
PRD("prd"), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.dangle.api.common.response | ||
|
||
import com.dangle.api.common.error.ErrorResponse | ||
import com.dangle.common.DangleErrorCode | ||
|
||
class ApiResponse<T> private constructor( | ||
val result: ResponseType, | ||
val data: T? = null, | ||
val error: ErrorResponse? = null, | ||
val debug: String? = null | ||
){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 SealedInterface 적용해보는건 어떨까요? sealed interface ApiResponse<T> {
val result: ResponseType
data class Success<T>(
val data: T?
) : ApiResponse<T> {
override val result = ResponseType.SUCCESS
}
data class Error<T>(
val error: ErrorResponse? = null,
val debug: String? = null
) : ApiResponse<T> {
override val result = ResponseType.ERROR
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sealed interface를 잘 사용해볼 버릇을 안해봐서 정확한 사용처? 에 대해서 아직 와닿지않는 것 같습니다 ㅎㅎ.. 혹시 sealed 키워드 (클래스, 인터페이스 포함) 를 사용하시는 기준이 있을까요? |
||
|
||
companion object{ | ||
fun<T> success(result: T): ApiResponse<T>{ | ||
return ApiResponse( | ||
result = ResponseType.SUCCESS, | ||
data = result, | ||
) | ||
} | ||
|
||
fun<T> success(result: List<T>) : ApiResponse<List<T>>{ | ||
return ApiResponse( | ||
result = ResponseType.SUCCESS, | ||
data = result | ||
) | ||
} | ||
|
||
fun<T> error(errorCode: DangleErrorCode,error: String?, debug:String? ):ApiResponse<T>{ | ||
return ApiResponse( | ||
result = ResponseType.ERROR, | ||
data = null, | ||
error = ErrorResponse( | ||
errorCode = errorCode, | ||
data = error, | ||
), | ||
debug = debug | ||
) | ||
} | ||
} | ||
enum class ResponseType{ | ||
SUCCESS,ERROR | ||
} | ||
|
||
override fun toString(): String { | ||
return "ApiResponse(result=$result, data=$data, error=$error, debug=$debug)" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
package com.dangle.common | ||
|
||
class DangleException( | ||
errorCode: DangleErrorCode, | ||
debug: String? = null, | ||
throwable: Throwable? = null, | ||
val errorCode: DangleErrorCode, | ||
val debug: String? = null, | ||
val throwable: Throwable? = null, | ||
) : RuntimeException("$errorCode${debug?.let { " - $it" }}", throwable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러코드 범위 확인했습니다~!