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
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.ReportAsSingleViolation;
import jakarta.validation.constraints.Pattern;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Pattern(regexp = "^[a-zA-Z0-9_-]{6,12}$", message = "아이디 형식이 올바르지 않습니다.")
@NotBlank
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
@ReportAsSingleViolation
public @interface LoginIdPattern {

String message() default "아이디는 6~12자의 영문, 숫자, 특수문자(-, _)만 사용 가능합니다.";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package life.mosu.mosuserver.global.exception;

import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import life.mosu.mosuserver.infra.notify.NotifyClientAdapter;
import life.mosu.mosuserver.infra.notify.dto.discord.DiscordExceptionNotifyEventRequest;
Expand All @@ -16,6 +18,7 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.reactive.resource.NoResourceFoundException;

Expand Down Expand Up @@ -126,7 +129,7 @@ public ResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}

@ExceptionHandler(NoResourceFoundException.class )
@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(Exception ex) {
notifyIfNeeded(ex);

Expand All @@ -140,12 +143,40 @@ public ResponseEntity<ErrorResponse> handleNotFound(Exception ex) {
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
public ResponseEntity<ErrorResponse> handleTypeMismatch(
MethodArgumentTypeMismatchException ex) {
notifyIfNeeded(ex);
ErrorResponse response = ErrorResponse.builder()
.status(HttpStatus.BAD_REQUEST.value())
.code("TYPE_MISMATCH")
.message("요청 파라미터 타입이 올바르지 않습니다.")
.message(ex.getMessage().toLowerCase(Locale.ROOT))
.build();

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity<?> handleHandlerMethodValidation(HandlerMethodValidationException ex) {
notifyIfNeeded(ex);
ErrorResponse response = ErrorResponse.builder()
.status(HttpStatus.BAD_REQUEST.value())
.code("TYPE_MISMATCH")
.message(ex.getMessage().toLowerCase(Locale.ROOT))
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
Comment on lines +158 to +167

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The exception handler for HandlerMethodValidationException has a couple of issues:

  1. The error code is set to "TYPE_MISMATCH", which is incorrect for a validation error. It should be "VALIDATION_ERROR" to be consistent with other validation-related handlers.
  2. Using ex.getMessage() for the error message can expose internal implementation details to the client, which is not ideal. It's better to extract just the validation message, similar to how it's done in handleConstraintViolation.

Here's a suggested improvement to extract a clean message and use the correct error code. You might need to add an import for org.springframework.context.MessageSourceResolvable if it's not already present.

    @ExceptionHandler(HandlerMethodValidationException.class)
    public ResponseEntity<?> handleHandlerMethodValidation(HandlerMethodValidationException ex) {
        notifyIfNeeded(ex);
        String message = ex.getAllValidationResults().stream()
            .flatMap(result -> result.getResolvableErrors().stream())
            .map(org.springframework.context.MessageSourceResolvable::getDefaultMessage)
            .findFirst()
            .orElse("유효성 검사에 실패했습니다.");
        ErrorResponse response = ErrorResponse.builder()
                .status(HttpStatus.BAD_REQUEST.value())
                .code("VALIDATION_ERROR")
                .message(message)
                .build();
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
    }


@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolation(ConstraintViolationException ex) {
notifyIfNeeded(ex);
String message = ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.findFirst()
.orElse("유효성 검사에 실패했습니다.");
ErrorResponse response = ErrorResponse.builder()
.status(HttpStatus.BAD_REQUEST.value())
.code("VAILDATION_ERROR")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a typo in the error code. It should be "VALIDATION_ERROR" instead of "VAILDATION_ERROR".

                .code("VALIDATION_ERROR")

.message(message)
.build();

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package life.mosu.mosuserver.presentation.user;

import life.mosu.mosuserver.application.user.UserService;
import life.mosu.mosuserver.global.annotation.LoginIdPattern;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.request.IsLoginIdAvailableResponse;
Expand All @@ -10,6 +11,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -18,6 +20,7 @@
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@Validated
public class UserController implements UserControllerDocs {

private final UserService userService;
Expand Down Expand Up @@ -46,7 +49,7 @@ public ResponseEntity<ApiResponseWrapper<CustomerKeyResponse>> getCustomerKey(

@GetMapping("/check-id")
public ResponseEntity<ApiResponseWrapper<IsLoginIdAvailableResponse>> isLoginIdAvailable(
@RequestParam String loginId
@LoginIdPattern @RequestParam String loginId
) {
Boolean isLoginIdAvailable = userService.isLoginIdAvailable(loginId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import life.mosu.mosuserver.global.annotation.LoginIdPattern;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.request.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.CustomerKeyResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "User", description = "사용자 관련 API")
@Validated
public interface UserControllerDocs {

@Operation(summary = "고객 키 조회", description = "사용자 ID를 이용해 결제에 사용될 고객 키(Customer Key)를 조회합니다.")
Expand All @@ -37,6 +40,6 @@ public ResponseEntity<ApiResponseWrapper<CustomerKeyResponse>> getCustomerKey(
@ApiResponse(responseCode = "500", description = "서버 내부 오류")
})
public ResponseEntity<ApiResponseWrapper<IsLoginIdAvailableResponse>> isLoginIdAvailable(
@RequestParam String loginId
@LoginIdPattern @RequestParam String loginId
);
}