RestApi ๋ง๋ค๊ธฐ - badRequest (4)
- ๊ฐ๋ฐ/restAPI๐ข
- 2021. 2. 13. 22:12

๋ง์ฝ์ ๊ฒ์ ์กฐ๊ฑด์ ์๋ชป ์
๋ ฅํ ๊ฒฝ์ฐ๋ ์ด๋ป๊ฒ ๋ ๊น?
์ต๋ํ ๊ฐ๋จํ๊ฒ ์์ฑํด๋ดค๋ค.
๋ง์ฝ์, ์ฃผ๋ฌธ ๋ ์ง๊ฐ ๋ฐฐ๋ฌ ์๋ฃ ๋ ์ง๋ณด๋ค ๋ฆ์ ๊ฒฝ์ฐ๋ผ๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
์์งํ ๋ง์ด ๋์ง ์๋๋ค.
์ด๋ด๋๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋ฅผ ํด์ผํ ๊น?
@Test
void badRequest() throws Exception {
DeliveryDto delivery = DeliveryDto.builder()
.item("book")
.user("klom")
.deliveryTime(LocalDateTime.now().plusDays(10))
.deliveryEndTime(LocalDateTime.now())
.itemPrice(0)
.build();
mockMvc.perform(post("/api/delivery/")
.accept(MediaTypes.HAL_JSON_VALUE)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(delivery))
)
.andDo(print())
.andExpect(status().isBadRequest())
;
}
๋ฌผ๋ก ์ง๊ธ์ ์ํ์ฝ๋๊ฐ 201์ด ๋์จ๋ค.
์๋ํ๋ฉด http์
์ฅ์์๋ ์ด ์ฝ๋๋ ํ๋ฆฐ ์ฝ๋๋ ์๋๊ธฐ ๋๋ฌธ์ด๋ค.

๊ฒฐ๊ณผ๋ ์์๊ณผ ๋ค๋ฅด๊ฒ ๋์๋ค.
์ด๋ป๊ฒ ํ๋ฉด ์ข์๊น?
์๊ฐํด๋ณด๋ ํ
์คํธ ์ฝ๋๋ ์์ ํด์ผ ๋ ๊ฒ ๊ฐ๋ค.
์๋ํ๋ฉด ์๋ฌด๊ฒ๋ ์
๋ ฅํ์ง ์๋์ํ์ผ๋๋ ์ํ์ฝ๋400์ด ๋์์ผ ๋๋ค.
@Test
void badRequest_empty_entity() throws Exception {
DeliveryDto delivery = DeliveryDto.builder()
.build();
mockMvc.perform(post("/api/delivery/")
.accept(MediaTypes.HAL_JSON_VALUE)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(delivery))
)
.andDo(print())
.andExpect(status().isBadRequest())
;
}
ํ์ง๋ง ์ด๊ฒ๋ ์ํ์ฝ๋๊ฐ 201์ด ๋์ค๊ฒ ๋๋ค. ์๋ํ๋ฉด ์์ค๋ ํ๋ฆฐ๊ฒ์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ก Validation์ ํ๋ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค.
๋ง์ฝ Validation์ด ์ ์์ ์ผ๋ก ๋์ด์์ง ์๋๋ค๋ฉด,
400์๋ฌ๊ฐ ๋์ค๊ฒ ํ๋ฉด ๋๋ค.
๊ฐ์ฅ ๋จผ์ dto์ Validation์ ์ถ๊ฐํด๋ณด์.
ํ์ง๋ง Validation์ด ์กด์ฌํ์ง ์๋๋ค. ์คํ๋ง๋ถํธ2.3๋ถํฐ์ธ๊ฐ Validation์ด web์์ ๋
๋ฆฝ๋์๊ธฐ ๋๋ฌธ์ด๋ค.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
์ด๊ฒ์ ์ถ๊ฐํ๋ฉด ์ ์์ ์ผ๋ก ๋์จ๋ค.
์, ์ด๊ฒ์ ์ถ๊ฐํ์ง ์์๋ @NotNull์ ๋์ค๊ธด ํ์ง๋ง Validation์ด ๋๋ ์ด๋
ธํ
์ด์
์ด ์๋๋ค.
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class DeliveryDto {
@NotNull
private String item;
@NotNull
private String user;
@NotNull
private LocalDateTime deliveryTime;
@NotNull
private LocalDateTime deliveryEndTime;
@Min(0)
private Integer itemPrice;
}
๊ทธ๋ฆฌ๊ณ ์ปจํธ๋กค๋ฌ์ ๊ฐ์
import org.springframework.validation.Errors;
import javax.validation.Valid;
public ResponseEntity<?> createDelivery(@RequestBody @Valid DeliveryDto deliveryDto, Errors errors) {
์ด๋ ๊ฒ ์์ ํ๋ค.
๊ทธ๋ฆฌ๊ณ
if(errors.hasErrors()) {
return ResponseEntity.badRequest().build();
}
์ด๊ฒ์ ์
๋ ฅํ๊ฒ ๋๋ฉด,
ockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
์ํ์ฝ๋๊ฐ 400์ด ๋์จ๋ค๋ ๊ฑธ ์ ์ ์๋ค.
๊ทธ๋ฌ๋ฉด ์ด๊ธฐ์ ๋ณด์ฌ์คฌ๋,
@Test
void badRequest() throws Exception {
DeliveryDto delivery = DeliveryDto.builder()
.item("book")
.user("klom")
.deliveryTime(LocalDateTime.now().plusDays(10))
.deliveryEndTime(LocalDateTime.now())
.itemPrice(0)
.build();
mockMvc.perform(post("/api/delivery/")
.accept(MediaTypes.HAL_JSON_VALUE)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(delivery))
)
.andDo(print())
.andExpect(status().isBadRequest())
;
}
์ด๊ฑด ์ฑ๊ณต์ด๋ค.
java.lang.AssertionError: Status expected:<400> but was:<201>
Expected :400
Actual :201
์ ๊ทธ๋ด๊น?
์ ์ด์ Validation์๋ ์๊ฐ ์ฒดํฌํ๋ ๊ฒ์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฌ๋ฉด Validation๋ฅผ ํด์ฃผ๋ ํด๋์ค๋ฅผ ๋ง๋๋๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค.
@Component
public class DeliveryValidation {
public void validate(DeliveryDto deliveryDto, Errors errors) {
if(deliveryDto.getDeliveryEndTime().isAfter(deliveryDto.getDeliveryTime())) {
errors.rejectValue("DeliveryTime", "wrong time");
}
}
}
์ด๋ฐ์์ผ๋ก ์๋ฌ๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ ์ปจํธ๋กค๋ฌ์
validation.validate(deliveryDto, errors);
if (errors.hasErrors()) {
return ResponseEntity.badRequest().build();
}
์ด ์ฝ๋๋ฅผ ์ถ๊ฐํ๊ฒ ๋๋ฉด,
MockHttpServletResponse:
Status = 400
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
์๋ต์ด ์ ์์ ์ผ๋ก ๋์๋๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
๊ทผ๋ฐ ์ฐ๋ฆฌ๋ rest-api๋ฅผ ๋ง๋ค๊ณ ์๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์๋ฌ ์ ๋ณด๋ ํ์ํ ์ง๋ ๋ชจ๋ฅธ๋ค.
๊ทธ๋ฌ๋ฉด
return ResponseEntity.created(createUri).body(deliver);
์ด๊ฒ ์ฒ๋ผ body์ error๋ฅผ ๋ฃ์ด ๋ณด๋ด๋ฉด ๋๋๊ฒ์ผ๊น?
์ด๊ฑด ์ ์ด์ ์๋ชป๋ ์๊ฐ์ด๋ค.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.validation.DefaultMessageCodesResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.validation.BeanPropertyBindingResult["messageCodesResolver"])
์ด๋ฐ ์๋ฌ๊ฐ ๋์ค๋๊ฒ์ ํ์ธ ํ ์ ์๋๋ฐ,
๊ทธ ์ด์ ๋ ์ ์ด์ ์ง๋ ฌํ๊ฐ ์ ์์ ์ผ๋ก ๋์ง ์์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฐธ๊ณ ๋ก delivery๊ฐ์ ๊ฒฝ์ฐ์๋ ์ ์ ํ ์๋ฐ๋น ์คํ์ ์ค์ํ๊ธฐ ๋๋ฌธ์ ์ง๋ ฌํ๊ฐ ๋๋ ๊ฒ์ด๋ผ๊ณ ํ๋ค.
๊ทธ๋ฌ๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
json์ ๋ง๋ค๋ฉด๋๋ค.
๋ค์์ json์ง๋ ฌํ๋ฅผ ๋ง๋๋ ์ฝ๋๋ค.
@JsonComponent
public class DeliverySerializer extends JsonSerializer<Errors> {
<>์๋ ์ง๋ ฌํ๋ฅผ ๋ง๋๋ ํด๋์ค๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ ์ํค๋ฉด...
๊ทธ๋ฌ๋ฉด
@Override
public void serialize(Errors errors, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
}
์ด๋ฐ๊ฒ ๋์ค๋๋ฐ,
errors๋ ์ฃผ์ฒด์ด๋ฉฐ,
jsonGenerator์ผ๋ก json์ ๋ง๋ค ์ ์๋ค.
serializerProvider์ ์ ๋ชจ๋ฅด๊ฒ ์ง๋ง, ์ง๋ ฌํ๋ฅผ ๋์์ฃผ๋ ๊ทธ๋ฐ ๊ฒ ๊ฐ๋ค.
๊ทธ๋ฌ๋ฉด ์ด๋ค์์ผ๋ก json์ ๋ง๋๋์ง ํ์ธํด๋ณด์.
์ฌ์ฉ๋๋ ๊ฑด ์ด๋ ๊ฒ 4(+1)์ธ๋ฐ, ํ๋๋ ๊ถ๊ธํด์ ์ผ๋จ ์์ฑํด๋ดค๋ค.
jsonGenerator.writeStartObject();
jsonGenerator.writeStartArray();
jsonGenerator.writeStringField("filedName","name");
jsonGenerator.writeFieldName("name");
jsonGenerator.writeString("???");
์ค์ json์ ๋ง๋ ๋ค๊ณ ์๊ฐํ๋ฉด ๋ง๋ค๊ธฐ๊ฐ ์ด๋ ต์ง๊ฐ ์๋ค.
๊ทผ๋ฐ ๋ญ ๊ทผ๊ฑฐ๋ก json์ ๋ง๋ค๊น?
์ฌ์ค ์๋ฌด์ด๋ฆ์ผ๋ก ํด๋ ์๊ด์ ์๋ค. ํ์ง๋ง ์ ์ด๋ ๋ญ๊ฐ ์๋์ง ์๋ค๋ฉด ๋ ์ข์ง ์์๊น?
๋๋ฒ๊ฑฐ๋ฅผ ํตํด ์ด๋ค๊ฒ์ด ์กด์ฌํ๋์ง ํ์ธ ํด๋ณด์.
๊ทธ๋ฌ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.

์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ง๋ค์ด๋ณด์.
error์๋ ์ด 2๊ฐ์ง๊ฐ ์๋๋ฐ, globalErrors์ FieldErrors์ด๋ ๊ฒ 2๊ฐ์ง๊ฐ ์กด์ฌํ๋ค.
ํ์ฌ ๋๋ FiledErrors๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์,
errors.getFieldErrors().forEach(e -> {
});
์ฝ๋๋ฅผ ์ด๋ ๊ฒ ์์ฑํ๋ค.
์ฌ๊ธฐ๋ ๋ฐฐ์ด๋ก ๋ง๋๋๊ฒ์ด ์ข์๊น? ์๋๋ฉด ๊ฐ์ฒด(JSON์ผ)๋ก ๋ง๋๋๊ฒ์ด ์ข์๊น?
๋ฆฌ์คํธ๋ก ๋์ด์๊ธฐ ๋๋ฌธ์ ๋ฐฐ์ด๋ก ๋ง๋๋๊ฒ์ด ์ข๋ค๊ณ ์๊ฐํ๋ค.
jsonGenerator.writeStartArray();
jsonGenerator.writeEndArray();
์ด๋ ๊ฒ ๋ฐฐ์ด์ ๋ง๋ค ์ ์๋ค.
์ด ์์ ํ์ํ ํ๋๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
๊ทธ๋ฌ๋ฉด ์ด๋ฐ ์ฝ๋๊ฐ ์์ฑ์ด ๋๋ค.
jsonGenerator.writeStartArray();
errors.getFieldErrors().forEach(e -> {
try {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("field", e.getField());
jsonGenerator.writeStringField("objectName", e.getObjectName());
jsonGenerator.writeStringField("code", e.getCode());
String result = e.getRejectedValue().toString();
if (result != null) {
jsonGenerator.writeStringField("rejectedValue", result);
}
jsonGenerator.writeEndObject();
} catch (IOException ioException) {
ioException.printStackTrace();
}
});
jsonGenerator.writeEndArray();
rejectedValue๋ ๋ฐํ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ๋ฐํ์ด ๋์ง ์์ ์ ์๊ธฐ ๋๋ฌธ์ null check๊ฐ ํ์๋ค.
์ ์ด๊ฒ์ ์คํํด๋ณด์.
๊ทธ๋ฌ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
Body = [{"field":"DeliveryTime","objectName":"deliveryDto","code":"wrong time","rejectedValue":"2021-02-13T22:07:37.794013"}]
*๊ฐ์ฒด๋ก ๋ง๋ค์ง ์์ผ๋ฉด
Can not write a field name, expecting a value๊ฐ ๋ฑ์ฅํ๋ค.
์ฐธ๊ณ ๋ก globalErrors๋
@Component
public class DeliveryValidation {
public void validate(DeliveryDto deliveryDto, Errors errors) {
if(deliveryDto.getDeliveryEndTime().isAfter(deliveryDto.getDeliveryTime())) {
errors.reject("DeliveryTime", "wrong time");
}
}
}
์ด๋ ๊ฒ ํ๋ฉด ๋๋ค. ์ฌ๊ธฐ์๋ filed๊ฐ์ด ์กด์ฌํ์ง ์๋๋ค.
'๊ฐ๋ฐ > restAPI๐ข' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| RestApi ๋ง๋ค๊ธฐ - spring-rest-docs (5) (0) | 2021.02.19 |
|---|---|
| RestApi ๋ง๋ค๊ธฐ - HATEOAS (5) (0) | 2021.02.16 |
| RestApi ๋ง๋ค๊ธฐ - ์ ๋ ฅ๊ฐ ์ ํํ๊ธฐ + ์ ๋ ฅ๊ฐ ์ด์ธ์ ์๋ฌ ๋ฐ์ (3) (0) | 2021.02.09 |
| RestApi ๋ง๋ค๊ธฐ - Repository์ถ๊ฐํ๊ธฐ (2) (0) | 2021.02.06 |
| RestApi ๋ง๋ค๊ธฐ - 201์ฝ๋ ์์ฑํ๊ธฐ (1) (0) | 2021.01.30 |