LoginSignup
25
17

More than 5 years have passed since last update.

go-swaggerでAPI定義(swagger.yml)からサーバーのコードを生成する

Last updated at Posted at 2018-06-17

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.yml
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になる)

生成されたコードの確認

コード生成を行うと、cmdmodelsrestapi以下にソースコードが生成されます。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の中身をいじっていくことになります。

メソッドの中身を実装する

生成されたソースコードの確認

改めて、生成されたソースコードを確認してみましょう。

get_foo_name_parameters.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が定義されます。

get_foo_name_responses.go
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}"}を返すような実装をしてみると、下記のようになります。

configure_swagger_exp1.go
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.yml

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に関する定義は下記のようになっています。

swagger_exp1_api.go
// GetBarHandler sets the operation handler for the get bar operation
GetBarHandler GetBarHandler

GetBarHandlerに関する型の情報はget_bar.goで確認できます。

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で確認します。

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に手動でハンドラーを登録するコードを書きます。

configure_swagger_exp1.go`
api.GetBarHandler = operations.GetBarHandlerFunc(func(params operations.GetBarParams) middleware.Responder {
    return operations.NewGetBarOK()
})

動作確認

サーバーを起動し直したら、curlで新しく追加したAPIを叩いてみましょう。

curl http://localhost:8080/bar

参考リンク

25
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
17