go-swaggerを使って、API定義(swagger.yml)からサーバーのソースコード(Go言語)を生成、APIの中身を実装していく方法についてまとめます。この投稿では、シンプルなEcho APIを実装してみるところまでをやります。
go-swaggerのインストール
Macの場合はbrew
でインストールできます。
brew tap go-swagger/go-swagger
brew install go-swagger
Mac以外のプラットフォームの場合はこちら
バージョンの確認
$ swagger version
version: 0.14.0
commit: 25e637c5028dee7baf8cdf5d172ccb28cb8e5c3e
プロジェクトディレクトリの作成
mkdir swaggerexp1
cd swaggerexp1
API定義(swagger.yml)の作成
swagger.yml
を作成し、中にAPIの定義を書きます。今回は/foo/{name}/
というパターンのURL(Method=GET
)のAPIを一つだけ定義してみます。
swagger: "2.0"
info:
title: "Swagger Exp 1"
version: "0.0.1"
paths:
/foo/{name}:
get:
parameters:
- name: "name"
in: "path"
required: true
type: "string"
responses:
200:
description: "Successful response"
schema:
type: "object"
properties:
message:
type: "string"
このAPIはURLに含まれる{name}
という文字列のパラメータを受け取ります。返り値はmessage
というstringのフィールドを含むJSONオブジェクトです。
バリデーション
swagger validate swagger.yml
swagger.yml
を書いたら、コード生成をする前にバリデーションをしておき、文法・内容に問題がないことを確認しておきます。必要な定義が欠如しているような場合は、エラーが表示されます。
コード生成
swagger generate server -f swagger.yml -A SwaggerExp1
バリデーションで問題がなければ、generate server
でサーバー側のソースコードを生成します。-A
パラメータで指定しているアプリ名は、生成されるソースコード内で使用されます。(例: 今回の場合API全体を定義するstructがSwaggerExp1API
になる)
生成されたコードの確認
コード生成を行うと、cmd
、models
、restapi
以下にソースコードが生成されます。go-swagger
を使った開発では、API定義の変更のたびにコードが生成され上書きされていくことになるため、ここで生成されたファイルの多くは、開発者自信で中身を書き換えてはいけません。
編集してよいのは、先頭に// This file is safe to edit. Once it exists it will not be overwritten
というコメントがついているファイル(今回の場合はconfigure_swagger_exp1.go
)のみとなります。※その代わり、このファイルは一度作成されると後にAPI定義を更新してコードを再生成しても変更が反映されないため、API定義の更新の際は手動で内容を定義に追随させていく必要があります。
生成されたサーバーを動かしてみる
必要なライブラリのインストール
生成されたサーバーを実行するためには、いくつかライブラリをインストールする必要があります。コード生成後のログにそれらのライブラリのインストールの案内が乗っていますが、dep
を使ってしまった方が後々のことを考えると楽です。
dep init
dep
がインストールされていない場合はbrew install dep
でインストールしましょう。(Macの場合)
起動してみる
go run cmd/swagger-exp1-server/main.go --port=8080
アクセスしてみる
curlでアクセスしてみると、APIの中身が実装されていない旨がレスポンスで帰ってきます。
$ curl http://127.0.0.1:8080/foo/Hello
"operation .GetFooName has not yet been implemented"
これはconfigure_swagger_exp1.go
に実装されている/foo/{name}
に対するハンドラーが下記のような実装になっているためです。
api.GetFooNameHandler = operations.GetFooNameHandlerFunc(func(params operations.GetFooNameParams) middleware.Responder {
return middleware.NotImplemented("operation .GetFooName has not yet been implemented")
})
APIの実装を進めていくためには、このconfigure_swagger_exp1.go
の中身をいじっていくことになります。
メソッドの中身を実装する
生成されたソースコードの確認
改めて、生成されたソースコードを確認してみましょう。
type GetFooNameParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
Name string
}
get_foo_name_parameters.go
には、GET /foo/name
がリクエストされた際のパラメータのパース結果を含むstructが定義されています。今回定義した/foo/{name}
の{name}
は、Name
というフィールドでアクセスできるようになっています。
api.GetFooNameHandler = operations.GetFooNameHandlerFunc(func(params operations.GetFooNameParams) middleware.Responder {
リクエストのハンドラーでは、引数としてこのstructが渡されるようになっており、APIリクエスト時のパラメータはこの引数から参照します。
例
name := params.Name
ハンドラーが返すべきレスポンスのオブジェクトはget_foo_name_responses.go
に定義されます。今回はレスポンスの方は一種類しか定義していませんが、結果によって複数の型を定義した場合は、ここに複数のレスポンスのstructが定義されます。
type GetFooNameOK struct {
/*
In: Body
*/
Payload *models.GetFooNameOKBody `json:"body,omitempty"`
}
get_foo_name_responses
には、GetFooNameOK
オブジェクトを生成するためのNewGetFooNameOK()
メソッドも定義されています。
func NewGetFooNameOK() *GetFooNameOK
レスポンスのオブジェクトの中身はmodels/get_foo_name_o_k_body.go
に定義されています。
type GetFooNameOKBody struct {
// message
Message string `json:"message,omitempty"`
}
get_foo_name_responses.go
には、GetFooNameOK
オブジェクトに内容を指定するためのWithPayload
メソッドも定義されているので、これらを組み合わせて下記のような形でレスポンスのオブジェクトを生成できます。
response := operations.NewGetFooNameOK().WithPayload(&models.GetFooNameOKBody{Message: "Blah Blah Blah"})
APIの実装
これらを踏まえて、GET /foo/{name}
が呼ばれた時に{"message": "Hello, {name}"}
を返すような実装をしてみると、下記のようになります。
func configureAPI(api *operations.SwaggerExp1API) http.Handler {
// ...略...
api.GetFooNameHandler = operations.GetFooNameHandlerFunc(func(params operations.GetFooNameParams) middleware.Responder {
return operations.NewGetFooNameOK().WithPayload(&models.GetFooNameOKBody{Message: "Hello, " + params.Name})
})
// ...略...
}
curlで確認する
$ curl http://localhost:8080/foo/Paul
{"message":"Hello, Paul"}
APIを追加する
もう一つAPIを追加してみましょう。
定義の追加
swagger.yml
を開いて、/bar
というパスのAPIを定義しましょう。今回は説明を読みやすくするため、パラメータ/レスポンス本文なしのAPIにします。
swagger: "2.0"
info:
title: "Swagger Exp 1"
version: "0.0.1"
paths:
/foo/{name}:
get:
parameters:
- name: "name"
in: "path"
required: true
type: "string"
responses:
200:
description: "Successful response"
schema:
type: "object"
properties:
message:
type: "string"
/bar:
get:
responses:
200:
description: OK
コードの再生成
API定義を追加したら、バリデーションを行い、問題なければ再びコード生成をします。
swagger validate swagger.yml
swagger generate server -f swagger.yml -A SwaggerExp1
API実装の追加
コードの再生成を行うと、新しく定義したAPI/bar
に対応したget_bar_parameters.go
などのファイルが新たに生成されます。しかし、configure_swagger_exp1.go
に関しては、コードの再生成を行っても更新されません。
最初にconfigure_swagger_exp1.go
が作られた時には、API/foo/{name}
に対するハンドラーの仮実装のコードが記入されていたため、その内容を書き換えて実装を進めればよかったですが、API追加時には自分でハンドラー登録のコードを書く必要があります。
APIハンドラーの確認
configure_swagger_exp1.go
ではoperations.SwaggerExp1API
のインスタンスapi
に対して、各API(パス)に対応したハンドラーを登録しています。swagger_exp1_api.go
を開くと、operations.SwaggerExp1API
に登録できるハンドラーの名前を確認できます。今回追加したGET /bar
に関する定義は下記のようになっています。
// GetBarHandler sets the operation handler for the get bar operation
GetBarHandler GetBarHandler
GetBarHandler
に関する型の情報はget_bar.go
で確認できます。
// GetBarHandlerFunc turns a function with the right signature into a get bar handler
type GetBarHandlerFunc func(GetBarParams) middleware.Responder
// Handle executing the request and returning a response
func (fn GetBarHandlerFunc) Handle(params GetBarParams) middleware.Responder {
return fn(params)
}
// GetBarHandler interface for that can handle valid get bar params
type GetBarHandler interface {
Handle(GetBarParams) middleware.Responder
}
レスポンスの型、メソッドはget_bar_responses.go
で確認します。
/*GetBarOK OK
swagger:response getBarOK
*/
type GetBarOK struct {
}
// NewGetBarOK creates GetBarOK with default headers values
func NewGetBarOK() *GetBarOK {
return &GetBarOK{}
}
APIハンドラーの登録
上記確認した定義を元に、configure_swagger_exp1.go
に手動でハンドラーを登録するコードを書きます。
api.GetBarHandler = operations.GetBarHandlerFunc(func(params operations.GetBarParams) middleware.Responder {
return operations.NewGetBarOK()
})
動作確認
サーバーを起動し直したら、curl
で新しく追加したAPIを叩いてみましょう。
curl http://localhost:8080/bar