Swaggerの使い方 - 分割と結合も紹介【Swagger】
Swaggerとは
SwaggerはREST APIのドキュメントや、テストできるUIを提供してくれるツールです。
APIのドキュメントはこれまでスプレッドシートなどで書かれることが多かったかと思いますが、Swaggerであれば、リッチなWebサイトのようなUIを提供してくれるので、見やすくかつ理解しやすいドキュメントを作ることができます。さらにパラメータを付与するなどして、返り値を確認するテストを実行することができます。
(GETメソッドのパラメータにbodyが用いられていますが、ミスです。ご容赦ください。)
Swaggerによるドキュメント作成の流れ
SwaggerはYAMLまたはJSONで記述され、そのフォーマットはSwagger Specificationといいます。このSpecificationを読み込ませると、先述のUIが出来上がります。Specificationを作成する基本的な方法が2つあります。Swagger Editorという公式に提供されたWeb上のエディタで編集し出力する方法と、ローカルでファイルを作成、編集する方法です。
Swagger Editorはウィンドウの左半分にSpecification、右には左で書かれた内容をリアルタイムに反映するUIで構成されています。ブラウザ内での記述が即座にUIに反映されて確認できるため、慣れていない場合にはオススメです。編集結果に満足したらSpecificationファイルを出力します。
ローカルで作成する場合は、YAMLまたはJSONファイルを作成し、フォーマットに則って記述します。
作成したSpecificationをSwagger Editorに読み込むことで、Swagger UIを確認することができます(APIのドキュメンテーションとして扱える)が、Dockerを用いて自分が用意する環境にホストすることもできます。Dockerではswagger-uiなどのimageが提供されています。方法についてはこちらで紹介しています。
Specificationの記述方法
ここからはSpecificationの書き方を、各構成要素を紹介する形で簡単に解説したいと思います。詳しい書き方は公式やQiita記事を参照ください。
Specificationは大きく下記の構成で成り立ちます。
- swagger
バージョンを指定 - host
APIのホスト先を記載 - schems
APIのスキーム(HTTP/HTTPS)記載 - basePath
APIのベースURLを記載(http://localhost:3000/xxxxx/xxx/api_pathの、/x~の部分) - produces
APIが生成するデータの形式を記載 - info
問い合わせ先などを記載(任意) - paths
APIのパス、引数、レスポンスなど各APIの詳細を記載 - definitions
APIのレスポンスなどに使用するデータ形式(型)を記載 - responses
ステータス返却など汎用的なレスポンスを記載
definitions、responsesは必須ではありませんが、汎用的な部品をまとめて記述量を減らすためにも利用すべきかと思います。Specificationの記述量はあっという間に膨らみますので、削れるところは削りましょう。
definitionsにexampleを記入することで、responseなどに内容の例示をすることができます。
Specificationの分割
Specificationの記述量はすぐに膨らみ、1つのファイルに書き連ねていくと見辛く編集しにくいファイルになってしまいます。
そこで、refという参照先を示すことができるプロパティを使用してファイルを分割していきます。分割したファイルは何らかの形で結合させる必要がありますが、今回はNode.jsによる例を紹介します。
ディレクトリ構造は下記のようにします。
index.yml update_api_document swagger.yml resolve.js definitions index.yml info index.yml paths index.yml <他各パスを定義した.yml> responses index.yml package.json yarn.lock node_modules
ルートディレクトリのindex.ymlは下記の通りです。swagger.ymlの骨格となる情報が記述されています。詳細部分は$refによって外部ファイルに委譲しています。
swagger: "2.0" host: localhost:3001 schemes: - http basePath: /noauth/v1 produces: - application/json info: $ref: ./info/index.yml paths: $ref: ./paths/index.yml definitions: $ref: ./definitions/index.yml responses: $ref: ./responses/index.yml
委譲先の各ディレクトリには必ずindex.ymlが配置され、先に読み込まれるようにしています。pathsのようにファイルを分けられるものは、各ディレクトリの中でも$refを用いて詳細を委譲します。
paths/index.yml
/orders: $ref: ./orders.yml /reservations: $ref: ./reservations.yml
paths/orders.yml
get: tags: - "注文管理" consumes: - "application/json" description: "注文一覧を取得" parameters: - description: "検索条件" in: "body" name: "body" required: true schema: $ref: "#/definitions/OrderFilter" produces: - "application/json" responses: 200: description: "ok" schema: type: "array" items: $ref: "#/definitions/OrderGet" 404: $ref: "#/responses/NotFound" summary: "注文一覧取得"
注意してほしいのは、参照を続けた先の末端のファイル(上記例ではpaths/orders.yml)の$refは、"#/~"となっているように同じファイル内を指していることです。これは結合後の状態を前提としているためです。
下記infoはただの一例ですが、補足情報を記載します。
info/index.yml
contact: email: "support@swagger.io" name: "API Support" url: "http://www.swagger.io/support" description: "This is a sample server celler server." license: name: "Apache 2.0" url: "http://www.apache.org/licenses/LICENSE-2.0.html" termsOfService: "http://swagger.io/terms/" title: "Swagger Example API" version: "1.0" tags: - name: "注文管理" description: "注文に関するAPI"
responseには、404などの汎用的なステータス返却などの情報を記述します。
responses/index.yml
BadRequest: description: 不正なリクエスト schema: $ref: "#/definitions/Error" Unauthorized: description: ログインしてください Forbidden: description: 権限がありません NotFound: description: 対象が存在しません InternalServerError: description: サーバエラー
続いてNode.jsファイルの内容を紹介します。
watch.jsファイルで、ルートディレクトリ以下に更新があったらプログラムを作動させswagger.ymlを更新するようにします。
watch.js
var chokidar = require('chokidar'); // ファイル監視モジュール var execSync = require('child_process').execSync; // コマンド実行用モジュール var watcher = chokidar.watch('.', { persistent: true, // 監視を続けている間プロセスを終了しない ignored: 'swagger.yml', // 監視対象外 ignoreInitial: true, // ファイルやフォルダの監視開始時にaddイベントやaddDirイベントを発生させない cwd: '.' // 基準パス }); watcher.unwatch('yarn.lock') // 監視対象外 .unwatch('package.json') .unwatch('node_modules'); var update_swagger_yml = path=> { // コマンドを実行する関数を変数化 console.log(path + ' changed. Update swagger.yml and copy it to viewer.'); execSync('./update_api_document.sh'); // 引数に指定されたファイルのコマンドを実行 } console.log('start watching...'); watcher.on('add', update_swagger_yml) // ファイルが新規作成された場合、第2引数を実行 .on('addDir', update_swagger_yml) // フォルダが新規作成された場合、第2引数を実行 .on('unlink', update_swagger_yml) // ファイルが削除された場合、第2引数を実行 .on('unlinkDir', update_swagger_yml) // フォルダが削除された場合、第2引数を実行 .on('change', update_swagger_yml); // ファイル内容が変更された場合、第2引数を実行
監視中のコマンドによってswagger.ymlが更新されるので、swagger.ymlは監視対象外にします。
上記watch.jsで指定されている「コマンド内容を記載したファイル」は、下記のようにします。
update_api_document.sh
#!/bin/sh node ./resolve.js > swagger.yml # resolve.jsの実行結果をswagger.ymlに出力 cp ./swagger.yml ../api/src/docs/yaml/ # swagger.ymlを右側(任意の場所)にコピー
resolve.jsは、$refを解決するプログラムです。
resolve.js
var resolve = require('json-refs').resolveRefs; var YAML = require('js-yaml'); var fs = require('fs'); var root = YAML.load(fs.readFileSync('./index.yml').toString()); // ./index.ymlをロード var options = { filter: ['relative', 'remote'], // relativeとremoteのrefを対象とする loaderOptions: { processContent: (res, callback) => { // responseの内容を利用するcallbackを定義 callback(null, YAML.load(res.text)); } } }; resolve(root, options).then( results => { console.log(YAML.dump(results.resolved)); // 解決した結果全てをyamlファイルとして出力 });
filterのrelativeとremoteは、相対パスと同じサーバーにあるパスが指定された$refを解決の対象にすることを意味します。他にlocal('#/~')やinvalid(無効なパス)があります。
watch.jsにより変更が監視され、変更があれば、update_api_document.shによってresolve.jsが実行されて、swagger.ymlが生成されます。.shの最後の行で、生成されたファイルの内容を別ディレクトリにコピーしていますが、これはdocker-composeで指定したswagger-uiの読み込み先です。docker-composeによるSwagger UIの使い方はこちらを参照ください。
手間はかかりますが、分割してみると、その見通しの良さに快感を覚えるはずです。
エラーになる時
Swagger UIで確認した時、definitionsが解決できないなどのエラーになる場合、resolveに失敗している可能性があります。
refのresolveプログラムはファイルの上から解決していくため、途中で失敗すると、以降のrefも解決されません。参照に指定されたものがdefinitionsに定義されていないなどがないか確認してみてください。
また、exampleなど文字列の中にVS Codeなどのエディターに表示されない不正な文字が含まれている場合もあります。ルール通りに見えるのに、エラーが起きてしまう場合には、一度definitionsなどをコピーしてSwagger Editorに貼り付けてみてください。Swagger Editorなら不正な文字を検出してくれますし、デバッグに便利です。
日付の範囲指定、月指定など - GoのSquirrelでの方法も紹介【MySQL】
日付の範囲を指定したり、7月だけといったように指定する方法を紹介します。
日付の範囲
// min ~ max WHERE <カラム名> BETWEEN min AND max // min ~ maxではない範囲 WHERE <カラム名> NOT BETWEEN min AND max
年、月、日の指定
// 年月日の指定 WHERE DATE_FORMAT(<カラム名>, %Y%m%d) = '20190714' // 年月の指定 WHERE DATE_FORMAT(<カラム名>, %Y%m) = '201907' // 年の指定 WHERE DATE_FORMAT(<カラム名>, %Y) = '2019' // 月日の指定 WHERE DATE_FORMAT(<カラム名>, %m%d) = '0714' // 月の指定 WHERE DATE_FORMAT(<カラム名>, %m) = '07' // 日の指定 WHERE DATE_FORMAT(<カラム名>, %d) = '14'
なお、Go言語でsquirrelというSQLビルダーがありますが、年の変数と月の変数などを連結して対象を指定する場合には、事前に連結した変数を定義し、それを使用する必要があります。
ym := year + month q = sq.Select( "id", "date", "customer_name", ). From("bookings"). Where(sq.Eq{"DATE_FORMAT(date, '%Y%m')": ym})
docker-composeでSwagger【Swagger】【Docker】
docker-compose経由でSwagger UIを起動する方法を紹介します。
docker-composeは.yamlにswaggerに関する命令を記述すれば大丈夫です。
services: ... swagger: image: swaggerapi/swagger-ui volumes: - ./api/src/docs/swagger.yaml:/swagger.yaml ports: - "8080:8080" environment: SWAGGER_JSON: /swagger.yaml # API_URL:
services配下にswaggerに関して記述します。
imageは公開されているswaggerapi/swagger-uiを指定します。
environment配下には、yamlまたはjsonファイルを読み込む方法を指定します。SWAGGER_JSONは、コンテナに配置したファイルを読み込む場合に使用します。API_URLはurlを指定して読み込む場合に使用します。
SWAGGER_JSONを使用する場合、volumesにはアップロードするyamlもしくはjsonファイルを指定します。
portsには、コロンを挟んで左側にホスト側のポート、右側に割り当てる先のコンテナのポートを指定します。
yamlはswaggerのルールに従って記述し、docker-compose.yamlに指定したディレクトリに配置します。
その上で、上記例ですと、ポート8080(例:localhost:8080)にアクセスすることでSwagger UIが表示されます。
swagger-uiの他にも、swagger-editorなどがあります。
dep パッケージの依存関係管理ツール【Go】
depはパッケージのバージョン・依存関係管理ツールで、Node.jsでいうところのnpmにあたるものです。
go getコマンドでインストールする場合と異なり、同じプロジェクト内で安全に同じバージョンのパッケージを使うよう管理することができます。チームで開発する時には必須とも言えるツールです。
go getの場合、インストールしたパッケージは$GOPATH/src/github.comに格納されますが、depの場合はプロジェクトのapi/src/vendorディレクトリに格納されます。
depでは2つのファイル「Gopkg.toml」と「Gopkg.lock」によってパージョン管理が行われます。
Gopkg.lockはプロジェクトのソースコードをスキャンし、importに指定されているパッケージの最新バージョンを設定として書き出されたファイルです。
さらにGopkg.tomlを編集することで、パッケージの使用するバージョンを指定することができます。
ソースコード上のimportの内容と、この2つのファイルによってインストールされるパッケージが決まり、vendorディレクトリにインストールされます。
- インストール
$ go get -u github.com/golang/dep/cmd/dep // または $ brew install dep $ brew upgrade dep
以下、コマンドを紹介します。
- プロジェクトの初期化
プロジェクト内に、Gopkg.toml、Gopkg.lock、vendorディレクトリが作成されます。
$ dep init
- 依存関係の安全確保
Gopkg.toml、Gopkg.lockとimportの内容に従って、vendorディレクトリにパッケージがインストールされます。
ただし、Gopkg.tomlに記載されていないパッケージは最新のバージョンがインストールされます。
$ dep ensure
- パッケージの最新化
Gopkg.tomlに指定したバージョンを無視して最新バージョンのパッケージをインストールします。
$ dep ensure -update
- 依存関係の安全確保(厳密)
Gopkg.toml、Gopkg.lockとimportの通りに、vendorディレクトリにパッケージがインストールされます。
Gopkg.tomlに指定されていないパッケージも最新化されることはありません。
$ dep ensure -vendor-only
- パッケージの追加
vendorディレクトリにパッケージがインストールされ、Gopkg.tomlとGopkg.lockにそのパッケージが指定されます。
ただし、importにそのパッケージを指定せず、dep ensureを行なった場合、Gopkg.lockとvendorからそのパッケージが削除されます。
$ dep ensure -add <パッケージ名>
dep を使用した Go の依存関係管理 | Boatswainブログ
Golangのパッケージバージョン管理をdepで行う - ブロックチェーンエンジニアとして生きる
command not foundと$PATH【Go】
パッケージをインストールして正しくcommandを入力しても実行できない場合、いわゆる「PATHが通っていない」状態かもしれません。
$ echo $GOPATH $ echo $PATH
で、それぞれの環境変数を確認することができます。
$GOPATHはgoの作業ディレクトリです。ファイルの読み込みなどの起点となります。
$PATHはインストールしたパッケージのコマンドなどが記述されたバイナリファイルが格納される場所です。
このPATHを明示的に設定する必要があります。
1行目のコマンドを実行した時に空行が返る場合、デフォルト値(go v1.8より)が適用されています。
デフォルト値は下記コマンドで確認できます。
$ go env | grep GOPATH
下記コマンドでPATHを通すことで、command not foundの事象は解決するかもしれません。
export GOPATH=$HOME/go; export PATH=$PATH:$GOPATH/bin;
ターミナルでexportを記述するとターミナルを起動するたびに実行する必要があるので、~/.bash_profileに環境変数として設定してしまうのが楽です。
bashの環境設定 - .bashrcと.bash_profile【Linux】 - 技術向上
swaggoの導入【Go】【Swagger】
swaggoという外部パッケージを利用することで、Swaggerを使って作成済みのGoのソースコードからAPIドキュメントを作成できます。
install
パッケージをインストールします。
$ go get github.com/swaggo/swag/cmd/swag
APIに関する記述を規定のフォーマットに従って、コメントとして記入します。
コメントの記入については公式を確認ください。
main.goがあるディレクトリで下記コマンドを実行し、swagのdocsを生成します。
$ swag init
net/httpパッケージを利用している場合は下記パッケージをインストールします。
パッケージによって手順が異なるので、詳細は公式を参照ください。
$ go get -u github.com/swaggo/http-swagger
ルーティングを行なっているパッケージのimportに下記を追加します。
ルートはご自身の環境に従って記入ください。
_ "github.com/~/docs" // swag initで生成したdocs。 httpSwagger "github.com/swaggo/http-swagger"
読み込んだdocsの内容がswaggerUIに出力されます。
ルーティングに下記を追加します。
r.Get("/swagger/*", httpSwagger.WrapHandler)
指定したルートにアクセスすると、swaggerUIが表示されるかと思います。
基本情報の定義
main.goに、base urlや基本情報を定義します。
BasePathに記載した内容は後述するAPIのどのルーティングにも最初に適用されるurlになります。
APIの定義が間違っていないのに、レスポンスがnot found になる場合は確認してみると良いかもしれません。
// @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:3001 // @BasePath /noauth/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 var addr = ":3001" func main() { ... }
APIの定義
ルーティングで指定しているメソッドが記述されている箇所の直上(空行があってはなりません)にルールに従ったコメントを記載することで、swagger UI上に表示されるようになります。
// GetCSTimetableDate godoc // @Summary Get a accCSTimetable of a specific day // @Description get string by ID // @Accept json // @Produce json // @Param cs_date body model.CSDate true "来店予約日" // @Success 200 {string} string "ok" // @Router /reservations/capacity/date [post] func (h *CSHandler) PostCSTimetableDate(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var csDate model.CSDate err := handler.GetJSON(r, &csDate) if err != nil { h.handleError(ctx, w, http.StatusBadRequest, "handler.GetJSON: "+err.Error()) return } ... }
@Paramですが、複数の値を付与するならば、objectやarrayを型に指定することができます。メソッドで使うオリジナルの型を定義することが多いかと思いますが、その型を指定することもできます。その時は「パラメータ名 body オリジナルの型」のように記述します。上記例では型の定義を独立させたclean architectureを採用しているプロジェクトからの抜粋のため、型名に「model.」と記載されています。
GitHub - swaggo/swag: Automatically generate RESTful API documentation with Swagger 2.0 for Go.
[swaggo]GoのGoDocを書いたら、Swaggerを出せるやばいやつ - Qiita