ConstructorPropertiesを使ったAPIリクエストモデルでは、ParameterObjectは使えないという話

f:id:excite-takayuki-miura:20220401172710p:plain

こんにちは。 エキサイト株式会社の三浦です。

JavaのSpring Bootでは、OpenAPIのライブラリを使用することでAPIのリクエストモデルを簡単にドキュメント化することができます。

ただ、リクエスト用のモデルで ConstructorProperties を使っていると、うまいことドキュメント化ができないことがあります。

今回は、 ConstructorProperties を使ったAPIリクエストモデルでは、 ParameterObject は使えないという話をしていきます。

Spring BootとOpenAPI

Spring Bootでは、OpenAPIのライブラリを使用することで、以下のようにAPIを簡単にドキュメント化できます。

build.gradle

plugins {
    // ...
    id "org.openapi.generator" version "5.4.0"
    id "org.springdoc.openapi-gradle-plugin" version "1.3.3"
    // ...
}

実コード

package sample;

import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("sample")
public class SampleController {

    @GetMapping
    public String info(@ModelAttribute SampleRequestModel sampleRequestModel) {
        return "ok";
    }
    
    @Value
    public static class SampleRequestModel {
        /**
         * サンプル文字列1
         */
        String sampleString1;

        /**
         * サンプル文字列2
         */
        String sampleString2;
    }
}

このコードによって、自動的に以下のようなドキュメントが生成されます。

f:id:excite-takayuki-miura:20220401170913p:plain

更に、以下のように @ParameterObject を付けると、より可読性が高まります。

    // ...

    @GetMapping
    public String info(@ModelAttribute @ParameterObject SampleRequestModel sampleRequestModel) {
        return "ok";
    }

    // ...

f:id:excite-takayuki-miura:20220401171131p:plain

このドキュメントはブラウザ上で見られるのですが、その画面から直接APIへのリクエストもできるため、とても便利なものとなっています。

しかし、実はリクエストモデル上で ConstructorProperties を使っていると、うまくドキュメント化されないという問題があります。

ConstructorPropertiesを使ったAPIリクエストモデルでは、ParameterObjectは使えない

リクエストモデルを、以下のようにしてみましょう。

    // ...

    @Value
    public static class SampleRequestModel {
        /**
         * サンプル文字列1
         */
        String sampleString1;

        /**
         * サンプル文字列2
         */
        String sampleString2;

        @ConstructorProperties({"test_string_1", "test_string_2"})
        public SampleRequestModel(String testString1, String testString2) {
            this.sampleString1 = testString1;
            this.sampleString2 = testString2;
        }
    }

sampleString1 はクエリパラメータ上では test_string_1 として、 sampleString2 はクエリパラメータ上では test_string_2 として受け取るので、ドキュメント上でも test_string_1test_string_2 のみが表示されていることが、こちらが意図する挙動となります。

ではまず、@ParameterObject を付けない状態でドキュメント化するとどうなるかというと、以下のようになります。

f:id:excite-takayuki-miura:20220401171629p:plain

なんとこのように、 ConstructorProperties の値とモデルのプロパティの値の両方が出てしまうのです。

更に @ParameterObject を付けると、以下のようになってしまいます。

f:id:excite-takayuki-miura:20220401171744p:plain

モデルのプロパティの値しか出ず、 ConstructorProperties の値は出てきません。

まとめ

残念ながら、現状では ConstructorProperties を使ったAPIリクエストモデルでは、 ParameterObject は使えないようです。

ConstructorProperties を使わずにリクエストモデルを作成するか、いつか対応されることを願いながら気長に待つのが良いかと思います。