요약
@Pattern
은enum
은 같이 쓸 수 없어서 커스텀 어노테이션을 만들어@JsonCreator
와 같이 사용해서 처리했다.
Request Body의 유효성 검증 중 Enum에 대한 유효성 검증은 로컬에서 따로 정해주지 않아도 400 Bad Request
로 Response가 넘어와서 그대로 배포를 진행했는데 빈 스트링을 넘겨줬을 때 500 에러가 넘겨졌다.
에러 로그를 살펴보니 json parse error 발생하였다.
보통 @Valid
어노테이션과 Validation이 제공하는 어노테이션을 사용하면 유효하지 않은 요청이 올 경우 MethodArgumentNotValidException을 발생시킨다. 그런데 Enum의 경우 해당 요청에 대한 Validation을 따로 제공하고 있지 않다.
public enum Duration {
HALF("HALF"),
HOUR("HOUR"),
HOUR_HALF("HOUR_HALF"),
TWO_HOUR("TWO_HOUR"),
TWO_HOUR_HALF("TWO_HOUR_HALF"),
THREE_HOUR("THREE_HOUR");
}
@AllArgsConstructor
public enum Place {
ONLINE("ONLINE"),
OFFLINE("OFFLINE"),
UNDEFINED("UNDEFINED");
}
public enum TimeSlot {
SLOT_6_00("06:00"),
SLOT_6_30("06:30"),
SLOT_7_00("07:00"),
SLOT_7_30("07:30"),
SLOT_8_00("08:00"),
SLOT_8_30("08:30"),
SLOT_9_00("09:00"),
SLOT_9_30("09:30"),
SLOT_10_00("10:00"),
SLOT_10_30("10:30"),
SLOT_11_00("11:00"),
SLOT_11_30("11:30"),
SLOT_12_00("12:00"),
SLOT_12_30("12:30"),
SLOT_13_00("13:00"),
SLOT_13_30("13:30"),
SLOT_14_00("14:00"),
SLOT_14_30("14:30"),
SLOT_15_00("15:00"),
SLOT_15_30("15:30"),
SLOT_16_00("16:00"),
SLOT_16_30("16:30"),
SLOT_17_00("17:00"),
SLOT_17_30("17:30"),
SLOT_18_00("18:00"),
SLOT_18_30("18:30"),
SLOT_19_00("19:00"),
SLOT_19_30("19:30"),
SLOT_20_00("20:00"),
SLOT_20_30("20:30"),
SLOT_21_00("21:00"),
SLOT_21_30("21:30"),
SLOT_22_00("22:00"),
SLOT_22_30("22:30"),
SLOT_23_00("23:00"),
SLOT_23_30("23:30"),
SLOT_24_00("24:00");
}
이렇게 Request Body에 사용해야하는 enum이 여러 개였는데, 클라이언트 측에서 enum value에 해당하지 않는 값을 넘겨주었을 경우 500 response가 넘어왔다.
enum을 검증하려면 커스텀 어노테이션을 만들어야 한다.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = EnumPatternValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumPattern {
String regexp();
String message() default "does not match \"{regexp}\"";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends java.lang.Enum<?>> enumClass();
}
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, Enum<?>> {
private Pattern pattern;
@Override
public void initialize(EnumPattern annotation) {
try {
pattern = Pattern.compile(annotation.regexp());
} catch (PatternSyntaxException e) {
throw new IllegalArgumentException("pattern regex is invalid", e);
}
}
@Override
public void isValid(Enum<?> value, ConstraintValidatorContext context) {
if (value == null) {
throw new BadRequestException(Error.INVALID_VALUE_EXCEPTION);
}
}
}
isValid()
제약 조건을 설정해서 value가 null이면 BadRequestException을 던지게 설정한다.
Validation에 대한 설정은 끝났다.
@JsonCreator를 enum 안 메서드에 적어주면 된다.
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static TimeSlot findByTime(String timeSlot) {
return Stream.of(TimeSlot.values())
.filter(c -> c.code.equals(timeSlot))
.findFirst()
.orElseThrow(() -> BadRequestException(Error.INVALID_VALUE_EXCEPTION));
}
}
그 후 Request Body에 Enum 값 위에 만든 @EnumPattern 을 통해 받아야 하는 형식을 지정해준다.
@EnumPattern(regexp = "\\d\\d:\\d\\d", message = "입력된 시간 형식이 유효하지 않습니다.", enumClass = TimeSlot.class)
private TimeSlot startTime;
그 후 만약 잘못 요청일 들어오면 MethodArgumentNotValidException이 발생한다.
이제 MethodArgumentNotValidException 에러를 Adviser에서 잡아준다.
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ErrorResponse handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) {
return ErrorResponse.error(Error.VALIDATION_REQUEST_MISSING_EXCEPTION);
}
잘못된 요청을 보내면 다음과 같이 유효성 검사를 실패했다는 메세지로 400 response를 넘겨준다.
Enum Validation 하는 과정은 끝입니다, 긴 글 읽어주셔서 감사합니다.
이걸 이제보다니.. ENUM만 BindingResult로 못잡아줘서 문제였는데 감사합니당~!