Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Springfox+Swagger+Bootprintによる即席REST API仕様書作成 ~制約編~

こんにちは、阪本です。

以前、「Springfox+Swagger+Bootprintによる即席REST API仕様書作成」というエントリーを書きましたが、今回はパラメータの制約をドキュメントに反映する方法について確認してみます。
なお、今回はSpringfoxのバージョンを2.3.1にしています。

@ApiModelPropertyによる制約の指定

まずは、Swagger Annotationを使ってパラメータの制約や説明の追加を行ってみます。

前回使用したEmployeeクラスに、@ApiModelPropertyアノテーションを追加します。

package swagger.entity;

import java.util.Date;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;

import io.swagger.annotations.ApiModelProperty;

public class Employee {
    private Integer id;
    private String  name;
    private Date    birthday;

    @ApiModelProperty(value = "Employee ID.", allowableValues = "range[1, 100]")
    @Range(min = 1, max = 100)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @ApiModelProperty(value = "Employee's name.", required = true, allowableValues = "range[0, 32]")
    @NotEmpty
    @Size(max = 32)
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ApiModelProperty(value = "Employee's birthday with ISO 8601 format.", required = true)
    @NotNull
    public Date getBirthday() {
        return this.birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

value属性にプロパティの説明、requiredに必須かどうか、allowableValuesに値の範囲を指定します。他にも、例を指定したりプロパティ名を変更したりもできますが、ここでは割愛します。

上のようにEmployeeクラスを記述してSpringBootアプリケーションを起動すると、次のようにSwagger UIに反映されます。

http://localhost:8080/swagger-ui.html
f:id:acro-engineer:20160113022055p:plain

画面右側のModel部分に情報が反映されていることがわかります。idプロパティのinteger部分にカーソルを合わせると、allowableValuesに指定した範囲も見えます。
ちなみに、日付フォーマット等の、必須・範囲以外の制約については、value属性に説明として記述する必要があります(専用の属性がありません)。

バリデーション用のアノテーションから制約条件を取得する

上で説明した方法では、JSR-303のアノテーション(@NotNullや@Size等)の情報を取ってきているのではなく、あくまで@ApiModelPropertyの値を取ってきてドキュメントを生成しているに過ぎません。
プログラムで動作するバリデーション用アノテーションとドキュメント用アノテーションを両方記述するのは、手間なのとズレが発生するのとで、避けたいところです。
ということで、バリデーション用のアノテーション(JSR-303等)から制約条件を取得できるように修正を加えます。

残念ながら、現時点(2016年1月)の最新バージョンであるSpringfox 2.3やSwagger 1.5では実現できないため、自前でコードを書く必要があります。
具体的には、ModelPropertyBuilderPluginインタフェースを実装したクラスを作成します。@Componentを付けて、SpringBootのComponentScan対象にしておきます。

package swagger;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.google.common.base.Optional;

import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.service.AllowableRangeValues;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;

@Component
public class Jsr303ModelPropertyBuilderPlugin implements ModelPropertyBuilderPlugin {

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    @Override
    public void apply(ModelPropertyContext context) {
        ModelPropertyBuilder builder = context.getBuilder();

        // プロパティのgetterを取得する
        Optional<BeanPropertyDefinition> beanPropDef = context.getBeanPropertyDefinition();
        BeanPropertyDefinition beanDef = beanPropDef.get();
        AnnotatedMethod method = beanDef.getGetter();
        if (method == null) {
            return;
        }

        // 必須・非必須を取得する
        NotNull notNull = method.getAnnotation(NotNull.class);
        NotEmpty notEmpty = method.getAnnotation(NotEmpty.class);
        if (notNull != null || notEmpty != null) {
            builder.required(true);
        }

        // 範囲制約を取得する
        Range range = method.getAnnotation(Range.class);
        if (range != null) {
            builder.allowableValues(new AllowableRangeValues(
                    Long.toString(range.min()), Long.toString(range.max())));
        }
        Size size = method.getAnnotation(Size.class);
        if (size != null) {
            builder.allowableValues(new AllowableRangeValues(
                    Long.toString(size.min()), Long.toString(size.max())));
        }
    }
}

Jsr303ModelPropertyBuilderPluginクラスのapplyメソッドで、各プロパティのgetterについているアノテーションを取得し、その内容に応じてModelPropertyBuilderに制約条件を設定しています。
#「Jsr303」というクラス名にしていますが、Hibernate Validatorのアノテーションも処理対象に加えています^^;

上記クラスを作成しておけば、次のように、Employeeクラスの@ApiModelPropertyから制約に関連する属性(requiredとallowableValues)を削除し、冗長な記述を排除できます。
(getterのみ抜粋して記載)

@ApiModelProperty(value = "Employee ID.")
@Range(min = 1, max = 100)
public Integer getId() {
    return this.id;
}

@ApiModelProperty(value = "Employee's name.")
@NotEmpty
@Size(max = 32)
public String getName() {
    return this.name;
}

@ApiModelProperty(value = "Employee's birthday with ISO 8601 format.")
@NotNull
public Date getBirthday() {
    return this.birthday;
}

これらを実施した上で再度SpringBootアプリケーションを起動すると、先ほどと同じSwagger UIページが生成されます。
必要に応じて対応アノテーションを増やす必要はありますが、冗長な記述は排除できました。

bootprint-swaggerで静的ドキュメント生成!

ということで、前回同様、bootprint-swaggerでHTMLのAPI仕様書を生成してみます。

f:id:acro-engineer:20160113024921p:plain

なかなか難しい表現が出てきました・・・(汗)
{ x ∈ Z | 1 ≤ x ≤ 100 } のZは、代数学で言うところの「整数の集合」を表しています。
あと、nameのところは (up to chars) と出力され、具体的な文字列長が出てきませんでした。
ちょっとイマイチ感がありますね。。

Bootprint側のカスタマイズは調べてみようと思います。

それでは。

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 競合は海外・外資!国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の求人 - Wantedly