diff --git a/moit-api/build.gradle.kts b/moit-api/build.gradle.kts index ef4887c2..102e1e57 100644 --- a/moit-api/build.gradle.kts +++ b/moit-api/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-validation") runtimeOnly("io.micrometer:micrometer-registry-prometheus") // security diff --git a/moit-api/src/main/kotlin/com/mashup/moit/common/ControllerExceptionAdvice.kt b/moit-api/src/main/kotlin/com/mashup/moit/common/ControllerExceptionAdvice.kt index f07c8ee3..2daa077f 100644 --- a/moit-api/src/main/kotlin/com/mashup/moit/common/ControllerExceptionAdvice.kt +++ b/moit-api/src/main/kotlin/com/mashup/moit/common/ControllerExceptionAdvice.kt @@ -2,42 +2,94 @@ package com.mashup.moit.common import com.mashup.moit.common.exception.MoitException import com.mashup.moit.common.exception.MoitExceptionType +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.AuthenticationException +import org.springframework.validation.BindException +import org.springframework.web.HttpRequestMethodNotSupportedException +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.MissingServletRequestParameterException import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException + @RestControllerAdvice class ControllerExceptionAdvice { + private val log: Logger = LoggerFactory.getLogger(ControllerExceptionAdvice::class.java) + @ExceptionHandler(Exception::class) - fun handleException(exception: Exception): ResponseEntity> { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body( - MoitApiResponse( - success = false, - data = null, - error = MoitApiErrorResponse( - code = MoitExceptionType.SYSTEM_FAIL.name, - message = exception.message, - ), - ) - ) + fun handleException(ex: Exception): ResponseEntity> { + log.error("Exception handler", ex) + return errorResponse(MoitExceptionType.SYSTEM_FAIL, ex.message) } @ExceptionHandler(MoitException::class) - fun handleMoitException(exception: MoitException): ResponseEntity> { - return ResponseEntity.status(exception.httpStatusCode) - .body( - MoitApiResponse( - success = false, - data = null, - error = exception.toApiErrorResponse(), - ) - ) + fun handleMoitException(ex: MoitException): ResponseEntity> { + log.error("MoitException handler", ex) + return errorResponse(ex.httpStatusCode, ex.toApiErrorResponse()) + } + + @ExceptionHandler(MissingServletRequestParameterException::class) + protected fun handleMissingServletRequestParameterException(ex: MissingServletRequestParameterException): ResponseEntity> { + log.error("MissingServletRequestParameterException handler", ex) + return errorResponse(MoitExceptionType.INVALID_INPUT, ex.message) + } + + @ExceptionHandler(BindException::class) + protected fun handleBindException(ex: BindException): ResponseEntity> { + log.error("BindException handler", ex) + return errorResponse(MoitExceptionType.INVALID_INPUT, ex.message) + } + + @ExceptionHandler(MethodArgumentTypeMismatchException::class) + protected fun handleMethodArgumentTypeMismatchException(ex: MethodArgumentTypeMismatchException): ResponseEntity> { + log.error("MethodArgumentTypeMismatchException handler", ex) + return errorResponse(MoitExceptionType.METHOD_ARGUMENT_TYPE_MISMATCH_VALUE, ex.message) + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException::class) + protected fun handleHttpRequestMethodNotSupportedException(ex: HttpRequestMethodNotSupportedException): ResponseEntity> { + log.error("HttpRequestMethodNotSupportedException handler", ex) + return errorResponse(MoitExceptionType.HTTP_REQUEST_METHOD_NOT_SUPPORTED, ex.message) + } + + @ExceptionHandler(AccessDeniedException::class) + protected fun handleAccessDeniedException(ex: AccessDeniedException): ResponseEntity> { + log.error("AccessDeniedException handler", ex) + return errorResponse(MoitExceptionType.ACCESS_DENIED, ex.message) + } + + @ExceptionHandler(AuthenticationException::class) + protected fun handleAuthenticationException(ex: AuthenticationException): ResponseEntity> { + log.error("AuthenticationException handler", ex) + return errorResponse(MoitExceptionType.AUTHENTICATION_FAILURE, ex.message) + } + + @ExceptionHandler(MethodArgumentNotValidException::class) + protected fun handleMethodArgumentNotValidException(e: MethodArgumentNotValidException): ResponseEntity> { + log.error("MethodArgumentNotValidException handler", e) + val errorMessage = e.allErrors.joinToString(" ,") + return errorResponse(MoitExceptionType.ARGUMENT_NOT_VALID, errorMessage) } private fun MoitException.toApiErrorResponse() = MoitApiErrorResponse( code = errorCode, message = message, ) + + private fun errorResponse(exceptionType: MoitExceptionType, message: String?) = errorResponse(exceptionType.httpStatusCode, MoitApiErrorResponse(exceptionType.name, message)) + private fun errorResponse(status: Int, errorResponse: MoitApiErrorResponse) = errorResponse(HttpStatus.valueOf(status), errorResponse) + + private fun errorResponse(status: HttpStatus, errorResponse: MoitApiErrorResponse): ResponseEntity> = ResponseEntity.status(status) + .body( + MoitApiResponse( + success = false, + data = null, + error = errorResponse, + ) + ) + } diff --git a/moit-api/src/main/kotlin/com/mashup/moit/controller/moit/dto/MoitDto.kt b/moit-api/src/main/kotlin/com/mashup/moit/controller/moit/dto/MoitDto.kt index f45e647d..c93e2328 100644 --- a/moit-api/src/main/kotlin/com/mashup/moit/controller/moit/dto/MoitDto.kt +++ b/moit-api/src/main/kotlin/com/mashup/moit/controller/moit/dto/MoitDto.kt @@ -75,7 +75,7 @@ data class MoitJoinRequest( @Schema(description = "moit 초대 코드") @field:NotBlank - @Size(min = 6, max = 6) + @field:Size(min = 6, max = 6) val invitationCode: String, ) diff --git a/moit-api/src/main/kotlin/com/mashup/moit/controller/study/dto/StudyDto.kt b/moit-api/src/main/kotlin/com/mashup/moit/controller/study/dto/StudyDto.kt index 61eee3d5..5d6f0b3d 100644 --- a/moit-api/src/main/kotlin/com/mashup/moit/controller/study/dto/StudyDto.kt +++ b/moit-api/src/main/kotlin/com/mashup/moit/controller/study/dto/StudyDto.kt @@ -51,7 +51,7 @@ data class StudyDetailsResponse( data class StudyAttendanceKeywordRequest( @Schema(description = "Study 참석 코드") @field:NotBlank - @Size(min = 4, max = 4) + @field:Size(min = 4, max = 4) val attendanceKeyword: String ) diff --git a/moit-common/src/main/kotlin/com/mashup/moit/common/exception/MoitExceptionType.kt b/moit-common/src/main/kotlin/com/mashup/moit/common/exception/MoitExceptionType.kt index afb1ec59..7c589ce4 100644 --- a/moit-common/src/main/kotlin/com/mashup/moit/common/exception/MoitExceptionType.kt +++ b/moit-common/src/main/kotlin/com/mashup/moit/common/exception/MoitExceptionType.kt @@ -16,6 +16,12 @@ enum class MoitExceptionType( SYSTEM_FAIL("Internal Server Error.", "C002_SYSTEM_FAIL", 500), INVALID_ACCESS("Invalid Access", "C003_INVALID_ACCESS", 403), ALREADY_EXIST("Already Exist", "C004_ALREADY_EXIST", 409), + INVALID_INPUT("Invalid Input", "C004_INVALID_INPUT", 400), + METHOD_ARGUMENT_TYPE_MISMATCH_VALUE("Request method argument type mismatch", "C005_TYPE_MISMATCH_VALUE", 400), + HTTP_REQUEST_METHOD_NOT_SUPPORTED("HTTP request method not supported", "C006_HTTP_METHOD_NOT_SUPPORTED", 400), + ACCESS_DENIED("Access denied. Check authentication.", "C007_ACCESS_DENIED", 403), + AUTHENTICATION_FAILURE("Authentication failed. Check login.", "C008_AUTHENTICATION_FAILURE", 401), + ARGUMENT_NOT_VALID("Method Argument Not Valid. Check argument validation.", "C009_ARGUMENT_NOT_VALID", 400), // ATTENDANCE ATTENDANCE_NOT_STARTED("스터디 출석이 아직 시작되지 않았습니다.", "A001_NOT_STARTED", 400),