はじめに
api開発の時に必須と言っても過言ではないswagger
ですが、なんと言っても保守し続けるのがとても面倒くさい、、、
できればコードから勝手にswaggerを生成してくれるといいのですが、、、そんな要望に対しての解決策の1つとなるのがこの
gin-swagger(https://github.com/swaggo/gin-swagger)
です。これはgoのコードに指定のコメントを追加していくことで自動的にswaggerのコードを作成していくためのツールです。
今回のゴール
今回はこのような簡単なCRUDのswaggerをgin-swagger
を用いて作成していきたいと思います!
ディレクトリツリーはこんな感じです!
rootディレクトリはgin-swagger-test
としています
gin-swagger-test
.
├── docs # ←自動生成されるディレクトリ
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── go.mod
├── go.sum
├── handlers
│ └── todo.go
├── main.go
├── models
│ └── todo.go
└── responses
├── errors.go
└── success.go
セットアップ
まずはセットアップです
$ go get -u github.com/swaggo/swag/cmd/swag
$ go get -u github.com/swaggo/gin-swagger
$ go get -u github.com/swaggo/files
# 下記コマンド実行後root配下に`docs`ディレクトリが作成される
$ swag init ./main.go
# もし`swag not found`的な警告が出た場合は下記コマンドを実行し改めて `swag init`を行う
$ export PATH=$(go env GOPATH)/bin:$PATH
参考文献:https://github.com/swaggo/swag/issues/197
package main
import (
"github.com/gin-gonic/gin"
// docsのディレクトリを指定
_ "gin-swagger-test/docs" // ←追記
ginSwagger "github.com/swaggo/gin-swagger" // ←追記
"github.com/swaggo/gin-swagger/swaggerFiles" // ←追記
)
func main() {
r := gin.Default()
// 下記を追記することで`http://localhost:8080/swagger/index.html`を叩くことでswagger uiを開くことができる
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run()
}
ベースのアノテーション
もっとたくさんのアノテーションがありますが、最低限必要なのは
- title
- version
- license.name
の3つのみです
より詳細なアノテーションをつけたい場合はこちらを参照してください
// @title gin-swagger todos
// @version 1.0
// @license.name kosuke
// @description このswaggerはgin-swaggerの見本apiです
func main() {
r := setupRouter()
r.Run()
}
handlerアノテーション
Getリクエスト
// GetTodos ...
// @Summary Todo一覧を配列で返す -> ①どのようなエンドポイントなのかを端的に示すコメント
// @Tags Todo -> ②tagの指定、tagベースでエンドポイントをグループ化する時に役立つ
// @Produce json -> ③どのような形のデータを返すかを指定
// @Success 200 {object} responses.SuccessResponse{data=[]models.Todo} -> ④successレスポンス
// @Failure 400 {object} responses.ErrorResponse -> ⑤errorレスポンス
// @Router /todos [get] -> ⑥ルーティング
func (t *Todo) GetTodos(c *gin.Context) {
todos, err := models.PostTodo() // ←何かしらのTodo配列を返す処理
if err != nil {
// エラーハンドリング
}
c.JSON(200, todos)
}
基本的にswaggerを書いたことのある人であれば「なるほどなぁ〜」となる内容だとは思うのですが、1点よくわからないところがあるとするなら
// @Success 200 {object} responses.SuccessResponse{data=[]models.Todo} -> ④successレスポンス
// @Failure 400 {object} responses.ErrorResponse -> ⑤errorレスポンス
上記のあたりでしょうか?
successレスポンス、errorレスポンスはそれぞれ
@Success ステータスコード 返り値の型 値
と言うような引数をとります。
また、todo配列のjsonを返したい場合はレスポンス用のstructを用意しそこに対して
responses.SuccessResponse{data=[]models.Todo}
と言うような形で配列のtodoを代入することで実現します
// SuccessResponse ...
type SuccessResponse struct {
Data interface{} `json: "data"`
}
// Todo ...
type Todo struct {
ID int `json:"id" example:"1"`
Title string `json:"title" example:"title1"`
Body string `json:"body" example:"body1"`
}
// ErrorResponse ...
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
Postリクエスト
// PostTodo ...
// @Summary 新規Todoを作成
// @Tags Todo
// @Accept json -> ①受け取るデータ型を指定
// @Produce json
// @Param title body string true "title" -> ②受け取るパラメータを指定
// @Param body body string true "body"
// @Success 201 {object} responses.SuccessResponse{data=models.Todo}
// @Failure 400 {object} responses.ErrorResponse
// @Router /todos [post]
func (t *Todo) PostTodo(c *gin.Context) {
todos, err := models.PostTodo() // ←何かしらの新規Todoを作成する処理
if err != nil {
c.JSON(400, &responses.ErrorResponse{Code: 400, Message: "bad request"})
}
c.JSON(200, todos)
}
postリクエストでつまずくところは
// @Param title body string true "title" -> ②受け取るパラメータを指定
// @Param body body string true "body"
@Param パラメータ名 パラメータの場所(header, body...) データ型 必須項目か? コメント
と言うような引数を与えることでパラメータを指定しています
Patchリクエスト
// PatchTodo ...
// @Summary 既存Todoを更新
// @Tags Todo
// @Accept json
// @Produce json
// @Param id path int true "id"
// @Param title body string false "title"
// @Param body body string false "body"
// @Success 200 {object} responses.SuccessResponse{data=models.Todo}
// @Failure 400 {object} responses.ErrorResponse
// @Router /todos/{id} [patch]
func (t *Todo) PatchTodo(c *gin.Context) {
todos, err := models.PatchTodo() // ←何かしらのTodoを更新する作成する処理
if err != nil {
c.JSON(400, &responses.ErrorResponse{Code: 400, Message: "bad request"})
}
c.JSON(200, todos)
}
Deleteリクエスト
// DeleteTodo ...
// @Summary 既存Todoを削除
// @Tags Todo
// @Accept json
// @Produce json
// @Param id path int true "id"
// @Success 201 {object} responses.SuccessResponse{data=models.Todo}
// @Failure 400 {object} responses.ErrorResponse
// @Router /todos/{id} [delete]
func (t *Todo) DeleteTodo(c *gin.Context) {
todos, err := models.DeleteTodo() // ←何かしらのTodoを削除する処理
if err != nil {
c.JSON(400, &responses.ErrorResponse{Code: 400, Message: "bad request"})
}
c.JSON(200, todos)
}
以上です。
アノテーションを修正後は改めて
$ swag init ./main.go
とすることで既存のswaggerを上書きしてくれます
まとめ
swaggerを自動生成するためにgin-swagger(swag)の書き方を覚えるのは多少めんどくさい部分があるのは否めないですが、自動生成をしてあげることで
- 書き方の統一感を出せる
- ドキュメントと実装の乖離する可能性を多少防げる
と言うメリットは確実にあるので
試しに採用する価値はあると思います。