개발강의정리/Spring

[스프링 기반 REST API 개발] 2-7. Event 생성 API 구현: Bad Request 응답 본문 만들기

nineDeveloper 2019. 12. 28.
728x90

스프링 기반 REST API 개발

2. 이벤트 생성 API 개발

포스팅 참조 정보

GitHub

공부한 내용은 GitHub에 공부용 Organizations에 정리 하고 있습니다

해당 포스팅에 대한 내용의 GitHub 주소

실습 내용이나 자세한 소스코드는 GitHub에 있습니다
포스팅 내용은 간략하게 추린 핵심 내용만 포스팅되어 있습니다

https://github.com/freespringlecture/spring-rest-api-study/tree/chap02-07_bad_request_response

해당 포스팅 참고 인프런 강의

https://www.inflearn.com/course/spring_rest-api/dashboard

실습 환경

  • Java Version: Java 11
  • SpringBoot Version: 2.1.2.RELEASE

7. Event 생성 API 구현: Bad Request 응답 본문 만들기

  • 응답 본문에 메세지가 있기를 바라고 메세지를 만드려면 어떻게 해야되는지
  • 응답 정보들은 Errors 객체에 들어있음
  • 어떤 필드에 어떤 값 때문에 왜 에러가 났는지 보여주고 클라이언트가 적절히 판단하도록 응답에 실어서 보내준다

Errors JSON 변환의 문제점

  • BeanSerializer을 사용해서 Java Bean Spec을 준수하는 객체를 JSON으로 변환할 수 있음
  • ObjectMapper에 여러가지 Serializer가 등록이 되어있음
  • ErrorsJava Bean Spec을 준수하는 객체가 아니라 JSON객체로 변환할 수가 없음
  • produces = MediaTypes.HAL_JSON_UTF8_VALUE 이 지정되어있어 응답 처리시 JSON으로 변환하는 처리를 하는데 변환 처리를 할 수 없어서 에러가 발생함

문제 해결을 위해 커스텀 JSON Serializer 만들기

  • extends JsonSerializer<T> (Jackson JSON 제공)
  • @JsonComponent: (스프링 부트 제공)매우 쉽게 등록할 수 있음

ErrorsSerializer 만들기

  • FieldErrorGlobalError를 둘다 맵핑해서 JSON에 담아줘야함
  • ObjectMapperErrors라는 객체를 Serialization 할 때 ErrorSerializer를 사용함

Errors

  • rejectValue: 필드 에러
  • reject: 글로벌 에러

BindingError

  • FieldErrorGlobalError (ObjectError)가 있음
  • objectName
  • defaultMessage
  • code
  • field
  • rejectedValue

테스트 할 것

  • 입력 데이터가 이상한 경우 Bad_Request로 응답
    • 입력값이 이상한 경우 에러
    • 비즈니스 로직으로 검사할 수 있는 에러
    • 에러 응답 메시지에 에러에 대한 정보가 있어야 한다

Bad Request 응답 본문 만들기 로직 작성

1. ErrorsSerializer 추가


package me.freelife.rest.common;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.validation.Errors;

import java.io.IOException;

@JsonComponent
public class ErrorsSerializer extends JsonSerializer<Errors> {
    @Override
    public void serialize(Errors errors, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeStartArray();
        errors.getFieldErrors().forEach(e -> {
            try {
                gen.writeStartObject();
                gen.writeStringField("field", e.getField());
                gen.writeStringField("objectName", e.getObjectName());
                gen.writeStringField("code", e.getCode());
                gen.writeStringField("defaultMessage", e.getDefaultMessage());
                Object rejectedValue = e.getRejectedValue();
                if (rejectedValue != null) {
                    gen.writeStringField("rejectedValue", rejectedValue.toString());
                }
                gen.writeEndObject();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        });

        errors.getGlobalErrors().forEach(e -> {
            try {
                gen.writeStartObject();
                gen.writeStringField("objectName", e.getObjectName());
                gen.writeStringField("code", e.getCode());
                gen.writeStringField("defaultMessage", e.getDefaultMessage());
                gen.writeEndObject();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        });
        gen.writeEndArray();
    }
}

2. EventValidator 로직 수정

price에 대한 validation 처리 조건 보완

package me.freelife.rest.events;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;

import java.time.LocalDateTime;

@Component
public class EventValidator {
    public void validate(EventDto eventDto, Errors errors) {
        if (eventDto.getBasePrice() > eventDto.getMaxPrice() && eventDto.getMaxPrice() > 0) {
            errors.reject("wrongPrices", "Values fo prices are wrong");
        }

        LocalDateTime endEventDateTime = eventDto.getEndEventDateTime();
        if (endEventDateTime.isBefore(eventDto.getBeginEventDateTime()) ||
                endEventDateTime.isBefore(eventDto.getCloseEnrollmentDateTime()) ||
                endEventDateTime.isBefore(eventDto.getBeginEnrollmentDateTime())) {
            errors.rejectValue("endEventDateTime", "wrongValue", "endEventDateTime is wrong");
        }

        // TODO BeginEventDateTime
        // TODO CloseEnrollmentDateTime
    }
}

3. 입력값 validation 테스트 데이터 검증 로직 보완


...

public class EventControllerTests {

    ...

    @Test
    @TestDescription("입력 값이 잘못된 경우에 에러가 발생하는 테스트")
    public void createEvent_Bad_Request_Wrong_Input() throws Exception {

        ...

        this.mockMvc.perform(post("/api/events")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(this.objectMapper.writeValueAsString(eventDto)))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$[0].objectName").exists())
                .andExpect(jsonPath("$[0].defaultMessage").exists())
                .andExpect(jsonPath("$[0].code").exists())
        ;
    }

}
728x90

댓글

💲 추천 글