Help us understand the problem. What is going on with this article?

[swaggo]GoのGoDocを書いたら、Swaggerを出せるやばいやつ

More than 1 year has passed since last update.

swaggo

今回紹介するswaggoyvasiyarov/swaggerにインスパイアを受けて、作成したOSSになります。現在、yvasiyarov/swaggerは開発が止まっているので、いくつかの問題が放置されたままになっています。(これはOSSなので仕方ないです)
swaggoは元の構文をそのまま流用して、機能追加をしているものになります。

  • Swagger 2.0の対応
  • 複雑な構造体の解析
  • カスタムヘッダー
  • example value
  • セキュリティ

私がざっと見た限り、上の内容はswaggoで無ければ使えないっぽいです。

swaggo.png

Swagger便利だけど、書くのがそもそもだるい

最近になって、よくSwaggerというワードを目にするようになりました。これはとても画期的で、APIに実際にリクエストを投げれるドキュメントが出来上がります。
しかし、このドキュメントのコードを書くのが結構面倒!

もし、既に実装されているコードからSwaggerを生み出すことが出来るものがあったら、最高ですよね?ということで、今回はswaggoを使ったドキュメント生成を紹介します。

実装方法

だらだら書いても仕方ないので、さくっとコード例を書きます。
今回の記事に使ったコードはこちらにあります。

General API Info

Swaggerの基本情報をmain.go(オプションで変えることも出来ます)に書きます。
設定出来る項目

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/swaggo/gin-swagger"
    "github.com/swaggo/gin-swagger/swaggerFiles"
    "github.com/swaggo/swag/example/celler/controller"
    _ "github.com/swaggo/swag/example/celler/docs"
)

// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// @BasePath /api/v1

// @securityDefinitions.basic BasicAuth

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization

// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information

// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
    r := gin.Default()

    c := controller.NewController()

    v1 := r.Group("/api/v1")
    {
        accounts := v1.Group("/accounts")
        {
            accounts.GET(":id", c.ShowAccount)
            accounts.GET("", c.ListAccounts)
            accounts.POST("", c.AddAccount)
            accounts.DELETE(":id", c.DeleteAccount)
            accounts.PATCH(":id", c.UpdateAccount)
            accounts.POST(":id/images", c.UploadAccountImage)
        }
        /...
    }
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    r.Run(":8080")
}

API Operation

エンドポイントに関する情報書きます。
設定出来る項目
よく使う項目をピックアップして説明致します。

package controller

//...

// ShowAccount godoc
// @Summary Show a account
// @Description get string by ID
// @Accept  json
// @Produce  json
// @Param  id path int true "Account ID"
// @Success 200 {object} model.Account
// @Failure 400 {object} controller.HTTPError
// @Failure 404 {object} controller.HTTPError
// @Failure 500 {object} controller.HTTPError
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
    id := ctx.Param("id")
    aid, err := strconv.Atoi(id)
    if err != nil {
        NewError(ctx, http.StatusBadRequest, err)
        return
    }
    account, err := model.AccountOne(aid)
    if err != nil {
        NewError(ctx, http.StatusNotFound, err)
        return
    }
    ctx.JSON(http.StatusOK, account)
}
エンドポイントの説明
// @Summary Show a account`
// @Description get string by ID`

MimeType

// @Accept  json
// @Produce  json

APIが許可しているMimeTypeを設定できます。
設定出来るMimeType

Param

// @Param パラメーター名 種類 型 必須要素か? コメント 
// @Param id path int true "Account ID"
// @Param q query string false "name search by q"
// @Param account body model.AddAccount true "Add account"
// @Param Authorization header string true "Authentication header"
  • path: パスパラメーター
  • query: クエリパラメータ
  • body: そのまんまです
  • header: カスタムリクエストヘッダー

パラメーターの型として、string, int, fileなどが利用が出来ます。

Result

// @Success ステータスコード パラメーターの型 データの型 コメント 
// @Success 200 {object} model.Account
// @Failure 400 {object} controller.HTTPError
// @Failure 404 {object} controller.HTTPError
// @Failure 500 {object} controller.HTTPError

成功のパターンと失敗のパターンを必要分だけ用意します。

構造体の指定の仕方

// @Success 200 {object} model.Account

上記のように書くと、package名.構造体名のような感じで探しに行って、ヒモ付を行なってくれます。

package model

// ...

type Account struct {
    ID   int    `json:"id" example:"1"`
    Name string `json:"name" example:"account name"`
}

exampleに設定された値は、Swaggerで出した時の具体例として設定されます。
jsonに設定された値は、JSONのキー名を決定します。
つまり、上記の例の場合、{"id": 1, "name": "account name"}というJSONが出来上がります。

Security

// @Security ApiKeyAuth

General API Infoで定義したセキュリティを使うことが出来ます。これの使い方については、別の記事で詳しく取り上げる予定です。

[https://swagger.io/docs/specification/2-0/authentication/:title]

Router

// @Router /accounts/{id} [get]

エンドポイントのURIとメソッドを指定します。このURIはGeneral API infoでのBasePathからの相対パスを設定する必要があります。

// @BasePath /api/v1

今回の場合だと、最終的に出来上がるエンドポイントは、/api/v1/accounts/{id}となります。

Swaggerコード生成

$ go get -u github.com/swaggo/swag/cmd/swag
$ swag init

上記をmain.goのあるパスで実行すると、docsフォルダが出来上がります。そこに、swaggerドキュメントに関するコードが生成されます。

.
├── README.md
├── controller
├── docs <-- これが自動生成される
│   ├── docs.go
│   └── swagger
│       ├── swagger.json
│       └── swagger.yaml
├── main.go
└── model
i -h
NAME:
   swag init - Create docs.go

USAGE:
   swag init [command options] [arguments...]

OPTIONS:
   --generalInfo value, -g value  Go file path in which 'swagger general API Info' is written (default: "main.go")
   --dir value, -d value          Directory you want to parse (default: "./")
   --swagger value, -s value      Output the swagger conf for json and yaml (default: "./docs/swagger")

ちなみに、このinitコマンドには、上記のようなオプションがあるので、自分の環境に合わせて変更が可能です。

//...
"/accounts/{id}": {
            "get": {
                "description": "get string by ID",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "summary": "Show a account",
                "operationId": "get-string-by-int",
                "parameters": [
                    {
                        "type": "integer",
                        "description": "Account ID",
                        "name": "id",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "schema": {
                            "type": "object",
                            "$ref": "#/definitions/model.Account"
                        }
                    },
                    "400": {
                        "schema": {
                            "type": "object",
                            "$ref": "#/definitions/controller.HTTPError"
                        }
                    },
                    "404": {
                        "schema": {
                            "type": "object",
                            "$ref": "#/definitions/controller.HTTPError"
                        }
                    },
                    "500": {
                        "schema": {
                            "type": "object",
                            "$ref": "#/definitions/controller.HTTPError"
                        }
                    }
                }
            },
//...

実際にSwaggerからリクエストを投げてみよう

swaggerのコードを読み込んで、使うのも良いですが、今回はswaggoが用意している便利な関数を使って、UIを呼び出したいと思います。ちなみに、そのコードは既に例に含まれています。

r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

http://localhost:8080/swagger/index.htmlには既にSwaggerUIが展開されており、リクエストが出来る状態になっています。
このハンドラは、gin Echo net/httpをサポートしています。対応していないものを使っている場合は、自分でSwaggerUIを用意する必要があります。(PR投げれば種類を増やせます!)

スクリーンショット 2018-03-12 13.34.33.png

まとめ

現状、簡単なAPIなら十分ドキュメント生成に活用出来ます。是非使ってみてください。
あと、自分が関わっているOSSということもあり、意見募集中です!w

halprogramming
専門学校HALのプログラミングが好きな人が集まっている同好会です。HALの在学生に限らず、OB, OGなども存在します。
https://www.hal.ac.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away