TIL

[TIL - 20230426] Enum 타입 필드 검증을 위한 커스텀 애노테이션 생성

Chef.Yeon 2023. 4. 26. 20:11

 

💻문제점

다음과 같이 job을 입력하지 않으면 에러가 발생한다. @Valid를 사용하려고 했지만, dto에서 job은 EnumType이므로 @NotBlank와 같은 애노테이션을 작성할 수 없었다.

public enum JobEnum {
    YOUTUBER("youtuber"),
    EDITOR("editor");

    private String job;

    JobEnum(String job) {
        this.job = job;
    }
}

📃시도

@NotNull 등 여러 애노테이션을 붙여봤지만, 전부 에러가 났다. 에러를 보니 애초에 @Valid 검증 단계로 넘어가기 전에, 문자열을 JSON으로 파싱하는 과정에서 오류가 발생했던 것이다.

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot coerce empty String ("") to `edit.edit.entity.JobEnum` value (but could if coercion was enabled using `CoercionConfig`)]

 payload의 job이 enumType인 JobEnum에 포함되지 않는 값이기 때문이다.


🔍해결

EnumClass를 작성할 때 JsonCreator 애노테이션을 작성해 파싱 메서드를 지정해 주었다.

JSON 파싱을 시도할 때 JobEnum에 없는 값이라면 null을, 제대로된 값을 가진다면 해당 value를 가질 것이다.

public enum JobEnum {
    YOUTUBER("youtuber"),
    EDITOR("editor");

    private String job;

    JobEnum(String job) {
        this.job = job;
    }

    public String getJob() {
        return job;
    }

    @JsonCreator(mode=JsonCreator.Mode.DELEGATING)
    public static JobEnum get(String code) {
        return Arrays.stream(JobEnum.values())
                .filter(type -> type.getJob().equals(code))
                .findAny()
                .orElse(null);
    }
}

 

검증을 위해 커스텀 EnumValid 애노테이션을 생성해주었다.

@Traget으로 적용대상을 지정하고, @Rentention을 통해 컴파일 이후 런타임에도 참조가 가능하도록 했다.

@Contraint를 통해 EnumValidator클래스를 통해 검증을 하도록 하였다.

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy=EnumValidValidator.class)
public @interface EnumValid {
    String message() default "Invalid Enum";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

이제, EnumType의 유효성 검증을 위해서 null인지 아닌지만 판단하면 된다.

public class EnumValidValidator implements ConstraintValidator<EnumValid, Enum<?>> {

    @Override
    public void initialize(EnumValid constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Enum<?> value, ConstraintValidatorContext context) {
        return value != null;
    }
}

 

message.properties에 enumvalid 메시지를 정의하여 사용했다.

@Getter
@Setter
@Builder
public class SignupRequestDto {

    @Size(min = 4, max = 10, message = "{userId.size}")
    @NotBlank
    @Pattern(regexp = "^[a-z0-9]*$", message = "{userId.pattern}")
    private String userId;

    @Size(min = 8, max = 15, message = "{password.size}")
    @NotBlank
    @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,15}", message = "{password.pattern}")
    private String password;

    @Email
    @NotBlank
    private String email;

    @Size(min = 2, max = 10)
    @NotBlank
    private String nickname;
    
    @EnumValid(message = "{job.enumvalid}")
    private JobEnum job;

    public Member toEntity(String encodedPassword) {
        return Member.builder()
                .userId(userId)
                .password(encodedPassword)
                .email(email)
                .nickname(nickname)
                .job(job)
                .build();
    }
}

728x90