こちらの続きです。
今回は Swagger を使って、api ドキュメントを書いてみます。

利用するリポジトリ

前回に引き続き以下を利用します。

何をやっているのか

今回肝になるのは Dockerfile の以下コマンドです。

RUN go install github.com/cosmtrek/air@v1.29.0 && \
  go install github.com/swaggo/swag/cmd/swag@v1.8.0

swag コマンドをインストールしています。
このコマンドを使うことで、コードから Swagger ドキュメントを生成することができます。

これに伴い、ルーティングに Swagger UI 用のパスを追加します。

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/gin-gonic/gin"
    // 以下2行が必要
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
)

// 以下は api ドキュメントの基本設定です
// これら以外にも利用できる定義がありますので、詳しくは https://swaggo.github.io/swaggo.io/ をご覧ください
//
// @title    General authentication API
// @version  1.0
// @license.name Kazuki Kamizuru
// @BasePath /
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
func main() {
    c := config.App{}

    if getEnv("APP_ENV", "dev") == "dev" {
        c.Debug = true
    }

    // アプリケーション初期化
    r := gin.Default()

    // デバッグ時のみルーティングを設定する
    if c.Debug {
        r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    }
}

ドキュメントを書く

実際に設定されている箇所はハンドラです。
ただ、ドキュメントを書くのは関数を定義している箇所ならどこでも良いみたいで、ルーティングに紐づいていなくとも Swagger UI には表示されていました(当然ですが、ルーティングに紐づけられていないため Swagger UI 上でテストしてもエラーになります)。

// app/interface/api/server/state.go

package server

import (
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gotoeveryone/auth-api/app/domain/entity"
    "github.com/gotoeveryone/auth-api/app/presentation/handler"
    "github.com/sirupsen/logrus"
)

type stateHandler struct{}

// NewStateHandler is state action handler
func NewStateHandler() handler.State {
    return &stateHandler{}
}

// Get is get application state
// @Summary Return application state
// @Tags State
// @Produce json
// @Success 200 {object} entity.State
// @Failure 404 {object} entity.Error
// @Failure 405 {object} entity.Error
// @Router /v1 [get]
func (h *stateHandler) Get(c *gin.Context) {
    c.JSON(http.StatusOK, entity.State{
        Status:      "Active",
        Environment: gin.Mode(),
        LogLevel:    logrus.GetLevel().String(),
        TimeZone:    time.Local.String(),
    })
}
  • /v1 というパスと Get 関数をマッピングする
  • Success/Failure にレスポンスのステータスコードと構造体を記載する
  • 提供する機能や形式など、その他読み手に与えたい情報を書く

これだけです。

どう見えるのか

実際のリクエストもできます。

パラメータも使ってみる

パラメータを使う場合は @Param を定義します。
以下はリクエストボディを使って POST リクエストを行う例です。

package server

import (
    "errors"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gotoeveryone/auth-api/app/domain/entity"
    "github.com/gotoeveryone/auth-api/app/domain/repository"
    "github.com/gotoeveryone/auth-api/app/presentation/handler"
)

type (
    // authenticateHandler is authentication action handler
    authenticateHandler struct {
        userRepo  repository.User
        tokenRepo repository.Token
    }
)

// ハンドラの初期化
func NewAuthHandler(ur repository.User, tr repository.Token) handler.AuthenticateHandler {
    return &authenticateHandler{
        userRepo:  ur,
        tokenRepo: tr,
    }
}

// Authenticate is execute authentication by user
// @Summary Execute authentication by user
// @Tags Authenticate
// @Accept  json
// @Produce json
// @Param data body entity.Authenticate true "request data"
// @Success 200 {object} entity.Token
// @Failure 400 {object} entity.Error
// @Failure 404 {object} entity.Error
// @Failure 405 {object} entity.Error
// @Router /v1/auth [post]
func (h *authenticateHandler) Authenticate(c *gin.Context) {
    // 実装は省略
}

定義する項目は、前から順に以下のとおりです。

  • リクエスト名
  • リクエスト形式 (body 以外だと path とか query とか)
  • 構造体
  • 必須かどうか
  • 説明

まとめ

今回は api ドキュメントについてざっくり説明しました。
ドキュメントをコードとは別で管理すると実態と乖離しやすくなるので、原則コードで完結できるのはとても良いなと思っています。
実際、開発中のプロダクトでもこの機能を使って api ドキュメントを書いており、加えてフロント側でそのスキーマ情報をもとに interface を作るというようなこともやっています。
このあたりはまた機会があれば紹介しようと思います。

次回はデータベースまわりについて書きます。

※2022/4/13追記
こちらにて続編を書きました。