概要
JAX-RSでJSONプロパティをEnumプロパティに変換する際にBean Validationで値の検証をします。
環境
-
WildFly 14.0.0.Final
ひとまず普通に実装してみる
作るのは最低限の3点セット+テストクラス。
テストクラスで設定しているパラメータの MONDAY
はわざと間違えて MONDAYY
にしています。
MyApplication.java
package org.katsumi.rest;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("api")
public class MyApplication extends Application
{
}
TestParam.java
package org.katsumi.rest.request;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.time.DayOfWeek;
public class TestParam
{
@NotNull
@Pattern(regexp = "(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)")
private DayOfWeek dayOfWeek;
public DayOfWeek getDayOfWeek()
{
return dayOfWeek;
}
public void setDayOfWeek(DayOfWeek dayOfWeek)
{
this.dayOfWeek = dayOfWeek;
}
}
TestResource.java
package org.katsumi.rest;
import org.katsumi.rest.request.TestParam;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.time.DayOfWeek;
import java.util.HashMap;
@Path("/test")
public class TestResource
{
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response postTest(@NotNull @Valid TestParam param)
{
final HashMap<DayOfWeek, String> map = new HashMap<>();
map.put(DayOfWeek.MONDAY, "月");
map.put(DayOfWeek.TUESDAY, "火");
map.put(DayOfWeek.WEDNESDAY, "水");
map.put(DayOfWeek.THURSDAY, "木");
map.put(DayOfWeek.FRIDAY, "金");
map.put(DayOfWeek.SATURDAY, "土");
map.put(DayOfWeek.SUNDAY, "日");
final HashMap<String, String> response = new HashMap<>();
response.put("dayOfWeek", map.get(param.getDayOfWeek()));
return Response.ok(response).build();
}
}
TestResourceTest.java
package org.katsumi.rest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.equalTo;
class TestResourceTest
{
@Test
void testJsonToEnum()
{
RestAssured.baseURI = "http://localhost:8080/json-to-enum/api";
RestAssured
.given()
.contentType(ContentType.JSON)
.body("{\"dayOfWeek\":\"MONDAYY\"}")
.when()
.post("/test")
.then()
.body("dayOfWeek", equalTo("月"));
}
}
実行してみる
実行すると下記のエラーが発生します。
javax.ws.rs.ProcessingException: RESTEASY008200: JSON Binding deserialization error
いや、エラーになることは望んでいたんですけど、 こういうのじゃなくてBean Validationのエラーになって欲しい。
修正してみる
という訳で、Bean Validationのエラーになるようにパラメータクラスを修正します。
TestParam.java
package org.katsumi.rest.request;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.time.DayOfWeek;
public class TestParam
{
@NotNull
@Pattern(regexp = "(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)")
- private DayOfWeek dayOfWeek;
+ private String dayOfWeek;
public DayOfWeek getDayOfWeek()
{
- return dayOfWeek;
+ return DayOfWeek.valueOf(dayOfWeek);
}
- public void setDayOfWeek(DayOfWeek dayOfWeek)
+ public void setDayOfWeek(String dayOfWeek)
{
this.dayOfWeek = dayOfWeek;
}
}
直接 DayOfWeek
にするのではなく、 String
で受け取って、使う際にgetterで DayOfWeek
で所得するように修正しました。
拡張
ここまでの対応でBean Validationによるチェックが行われるようになるのですが、 それを確認する方法がHTTPステータスコードぐらいしかありません。 そこで、Bean ValidationのエラーをJSONで返却するように下記のファイルを追加します。
ValidationError.java
package org.katsumi.rest.mapper;
public class ValidationError
{
private String path;
private String message;
public String getPath()
{
return path;
}
public String getMessage()
{
return message;
}
public ValidationError(String path, String message)
{
this.path = path;
this.message = message;
}
}
ConstraintViolationExceptionMapper.java
package org.katsumi.rest.mapper;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.util.List;
import java.util.stream.Collectors;
@Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException>
{
@Override
public Response toResponse(ConstraintViolationException e)
{
final List<ValidationError> errors = e.getConstraintViolations().stream()
.map(cv -> new ValidationError(
cv.getPropertyPath().toString(), cv.getMessage()))
.collect(Collectors.toList());
return Response.status(Response.Status.BAD_REQUEST)
.type(MediaType.APPLICATION_JSON)
.entity(errors)
.build();
}
}
レスポンスの形式が変わるので、テストクラスも修正します。
TestResourceTest.java
package org.katsumi.rest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.equalTo;
class TestResourceTest
{
@Test
void testJsonToEnum()
{
RestAssured.baseURI = "http://localhost:8080/json-to-enum/api";
RestAssured
.given()
.contentType(ContentType.JSON)
.body("{\"dayOfWeek\":\"MONDAYY\"}")
.when()
.post("/test")
.then()
- .body("dayOfWeek", equalTo("月"))
+ .body("message[0]", equalTo("must match \"(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)\""))
+ .body("path[0]", equalTo("test.arg0.dayOfWeek"));
}
}
これで実行するとテストケースが正常終了します。