1
0

ConstraintViolationExceptionからパラメタ名が取得できない

Last updated at Posted at 2024-03-30

要約

  • Spring BootでAPIを開発しており、パスパラメタへバリデーションを設定した
  • バリデーションを通過できなかったときにレスポンスボディへパラメタ名と理由を設定したい
  • しかしパラメタ名の取得でつまづいたのでその記録を記載

Controllerの例

OrderController.java
@RestController
@Validated
@RequestMapping("/api/")
public class OrderController {

    @PostMapping("/order/{pathOrderId}")
    public ResponseEntity<OrderResponse> postOrder(
      @PathVariable(name = "pathOrderId") @Size(min = 1, max = 6) String pathOrderId,
      @RequestBody @Validated OrderRequest request){
        // ...
    }

}

この例では、/api/order/1234567 へリクエストするとpathOrderIdのSize上限のため通過できずConstraintViolationExceptionがスローされる。
このとき、@RestControllerAdviceで捕捉してエラー情報をボディに設定して応答したい。
たとえばこのようなレスポンスにしたいとする。

{
  "item": "pathOrderId",
  "reason": "1 から 32 の間のサイズにしてください"
}

ConstraintViolationExceptionからこれらの情報を取得しようと考えていたが、目当てのものは存在せず思ったよりも難しいことが分かった。

ConstraintViolationから取得を試みる

ConstraintViolationException#getConstraintViolations() でConstraintViolationを取得できる。
このなかの#getPropertyPath()が使用できそうなので、試してみる。
インターフェース ConstraintViolation

試したコード

ControllerExceptionHandler.java
@RestControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        List<OrderResponseValidationResult> relList = new ArrayList<>();

        ex.getConstraintViolations().forEach(cv -> {
            OrderResponseValidationResult rel = new OrderResponseValidationResult(cv.getPropertyPath().toString(), cv.getMessage());
            relList.add(rel);
        });
        oRes.setValidationResults(relList);

        return handleExceptionInternal(ex, oRes, headers, HttpStatus.BAD_REQUEST, request);
    }

OrderResponseValidationResult.java
public class OrderResponseValidationResult {
    String item;
    String reason;

    public OrderResponseValidationResult() {};

    public OrderResponseValidationResult(String item, String reason){
        this.item = item;
        this.reason = reason;
    }
}

結果

/api/order/1234567 へリクエストすると、パラメタ名が取得できていない。

{
  "item": "postOrder.arg1",
  "reason": "1 から 32 の間のサイズにしてください"
}

対応方法

調べてみたが簡単に取得するすべがないように見えるので、次のような方法に妥協した。

案1:パラメタ名へ置換する

  • "postOrder.arg1"をKeyにしたMapを用意する
  • Valueへ実際のパラメタ名を設定する
ControllerExceptionHandler.java
@RestControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
    // パスパラメタ、クエリパラメタを変換するためのMap
    private static final Map<String, String> PARAMS = new HashMap<String, String>() {
        {
            // 今後パラメタが増えたらここに追加する
            put("postOrder.arg1", "pathOrderId");

        }
    };

    // パラメタが取得できた場合は置換したパラメタ名をreturn
    public String getParameterName(String name){
        return PARAMS.get(name) != null ? PARAMS.get(name) : name;
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        List<OrderResponseValidationResult> relList = new ArrayList<>();

        ex.getConstraintViolations().forEach(cv -> {
            // パスパラメタを置換してレスポンスへセット
            OrderResponseValidationResult rel = new OrderResponseValidationResult(getParameterName(cv.getPropertyPath().toString()), cv.getMessage());
            relList.add(rel);
        });
        oRes.setValidationResults(relList);

        return handleExceptionInternal(ex, oRes, headers, HttpStatus.BAD_REQUEST, request);
    }

これで当初期待したレスポンスが得られた。望ましい方法ではないがとりあえず良しとする。

{
  "item": "pathOrderId",
  "reason": "1 から 32 の間のサイズにしてください"
}

案2:パスパラメタのバリデーションの失敗は詳細をレスポンスしない

設計を変えて、ConstraintViolationExceptionではバリデーションの詳細を応答するのはやめてしまう。

ControllerExceptionHandler.java
@RestControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        List<OrderResponseValidationResult> relList = new ArrayList<>();

        ex.getConstraintViolations().forEach(cv -> {
            // パスパラメタを置換してレスポンスへセット
            OrderResponseValidationResult rel = new OrderResponseValidationResult(getParameterName(null, "Invalid parameters");
            relList.add(rel);
        });
        oRes.setValidationResults(relList);

        return handleExceptionInternal(ex, oRes, headers, HttpStatus.BAD_REQUEST, request);
    }

結果は次のとおり。

{
  "item": null,
  "reason": "Invalid parameters"
}

まとめ

  • 妥協した方法で今回は実装するが、もっとスマートな方法で実装したい
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0