読者です 読者をやめる 読者になる 読者になる

Swagger JAX-RS で同名のクラスを ApiModel にする時の注意点

メモ:Swagger JAX-RS で同名のクラス(別パッケージ)を ApiModel(戻り値) として定義する時に気を付けること。
Swagger のバージョンは 1.5.2-M1 です。

吐かれる JSON 内の Model 定義には、クラスの simpleName が使われます。
パッケージ違いを考慮しないので、同名のクラスがあると1つしか定義が残りません。

@ApiModel#value で別名を指定すると JSON に定義は吐かれますが、今度はそれを使ってる箇所のリンクが上手く吐かれません。

詳しいことはコードと JSON 見た方が分かりやすそう。

package sample.swagger_modelname.problem;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;

@Path("/problem")
@Api(value="/problem", description="モデル定義が消えるAPI")
public class ProblemModelResource {
  @GET
  @ApiOperation(value="サンプル", httpMethod="get", 
    nickname="get", response=ProblemModelResult.class)
  public ProblemModelResult get() {
    return new ProblemModelResult();
  }
}
package sample.swagger_modelname.problem;

import com.wordnik.swagger.annotations.ApiModel;
import com.wordnik.swagger.annotations.ApiModelProperty;

@ApiModel
public class ProblemModelResult {
  @ApiModelProperty
  public ProblemBean bean;
  @ApiModelProperty
  public sample.swagger_modelname.otherpackage.ProblemBean otherBean;
}
package sample.swagger_modelname.problem;

import com.wordnik.swagger.annotations.ApiModel;

@ApiModel(description="ProblemBean:こっちの定義が消える")
public class ProblemBean {
  public String field1;
}
package sample.swagger_modelname.otherpackage;

import com.wordnik.swagger.annotations.ApiModel;

@ApiModel(description="otherpackage.ProblemBean:こっちの定義が残る")
public class ProblemBean {
  public String field2;
}

こんなクラス群があった時に、吐かれる Swagger JSON は

{
    "swagger":"2.0",
    "tags":[
        {
            "name":"problem",
            "description":"モデル定義が消えるAPI"
        }
    ],
    "paths":{
        "/problem":{
            "get":{
                "tags":[
                    "problem"
                ],
                "summary":"サンプル",
                "description":"",
                "operationId":"get",
                "responses":{
                    "200":{
                        "description":"successful operation",
                        "schema":{
                            "$ref":"#/definitions/ProblemModelResult"
                        }
                    }
                }
            }
        }
    },
    "definitions":{
        "ProblemModelResult":{
            "properties":{
                "bean":{
                    "$ref":"#/definitions/ProblemBean"
                },
                "otherBean":{
                    "$ref":"#/definitions/ProblemBean"
                }
            }
        },
        "ProblemBean":{
            "properties":{
                "field2":{
                    "type":"string"
                }
            },
            "description":"otherpackage.ProblemBean:こっちの定義が残る"
        }
    }
}

definitions の中には ProblemBean が1つしかありません。

これを回避するにはこんな感じで @ApiModel#value を明示的に指定します。
さっきのコードとほぼ同じ構成で、別パッケージの Bean に @ApiModel#value を指定したものです。

package sample.swagger_modelname.otherpackage;

import com.wordnik.swagger.annotations.ApiModel;

@ApiModel(description="otherpackage.LinkErrorBean:定義が残るが、プロパティのリンクで問題が出る"
  , value="otherpackage.LinkErrorBean")
public class LinkErrorBean {
  public String field2;
}

但しこれだけだと今度は別の問題が出ます。

{
    "swagger":"2.0",
    "tags":[
        {
            "name":"linkerror",
            "description":"モデル定義が消えないけど、リンクがおかしいAPI"
        }
    ],
    "paths":{
        "/linkerror":{
            "get":{
                "tags":[
                    "linkerror"
                ],
                "summary":"サンプル",
                "description":"",
                "operationId":"get",
                "responses":{
                    "200":{
                        "description":"successful operation",
                        "schema":{
                            "$ref":"#/definitions/LinkErrorModelResult"
                        }
                    }
                }
            }
        }
    },
    "definitions":{
        "LinkErrorModelResult":{
            "properties":{
                "bean":{
                    "$ref":"#/definitions/LinkErrorBean"
                },
                "otherBean":{
                    "$ref":"#/definitions/LinkErrorBean"
                }
            }
        },
        "LinkErrorBean":{
            "properties":{
                "field1":{
                    "type":"string"
                }
            },
            "description":"LinkErrorBean:定義が残るが、プロパティのリンクで問題が出る"
        },
        "otherpackage.LinkErrorBean":{
            "properties":{
                "field2":{
                    "type":"string"
                }
            },
            "description":"otherpackage.LinkErrorBean:定義が残るが、プロパティのリンクで問題が出る"
        }
    }
}

difinitions には LinkErrorBean が二つ*1ありますが、
LinkErrorModelResult の properties の otherBean の参照先が #/definitions/LinkErrorBean になっており正しくありません。

これを回避するには、@JsonRootName に @ApiModel#value と同じ値を指定すると回避出来ました。

package sample.swagger_modelname.otherpackage;

import com.fasterxml.jackson.annotation.JsonRootName;
import com.wordnik.swagger.annotations.ApiModel;

@JsonRootName("otherpackage.NormalBean")
@ApiModel(description="otherpackage.NormalBean:両方定義が残る", value="otherpackage.NormalBean")
public class NormalBean {
  public String field2;
}

これで吐かれる JSON が正しい結果になりました。

{
    "swagger":"2.0",
    "tags":[
        {
            "name":"normal",
            "description":"モデル定義が消えないAPI"
        }
    ],
    "paths":{
        "/normal":{
            "get":{
                "tags":[
                    "normal"
                ],
                "summary":"サンプル",
                "description":"",
                "operationId":"get",
                "responses":{
                    "200":{
                        "description":"successful operation",
                        "schema":{
                            "$ref":"#/definitions/NormalModelResult"
                        }
                    }
                }
            }
        }
    },
    "definitions":{
        "NormalBean":{
            "properties":{
                "field1":{
                    "type":"string"
                }
            },
            "description":"NormalBean:両方定義が残る"
        },
        "otherpackage.NormalBean":{
            "properties":{
                "field2":{
                    "type":"string"
                }
            },
            "description":"otherpackage.NormalBean:両方定義が残る"
        },
        "NormalModelResult":{
            "properties":{
                "bean":{
                    "$ref":"#/definitions/NormalBean"
                },
                "otherBean":{
                    "$ref":"#/definitions/otherpackage.NormalBean"
                }
            }
        }
    }
}

ちょっと面倒なのでなるべくクラス名は被らない方が楽でいいですね!

全体のコードはこちら
sample/swagger_modelname at master · OdaShinsuke/sample · GitHub

*1:LinkErrorBean/otherpackage.LinkErrorBean