go-swagger を使って swagger から Goのサーバとクライアントのコードを生成する


swaggerの定義ファイルから[go-swagger](https://github.com/go-swagger/go-swagger)を使ってGoで書かれたサーバとクライアントのコードを生成して,実際にAPIを叩いてみようと思います

[公式ドキュメント](https://goswagger.io/)はこちら

## swagger とは
swagger とは RESTful API の仕様を定義したオープンソースのフレームワークです
マイクロソフト, Google, IBM などが参加する 「OPEN API Initiative」 という団体が The Linux Foundation の協力のもと推進しています
サーバ, クライアントのコード以外にもテストやドキュメントの生成もすることができます


## インストール
### install from source
```shell
go get -u github.com/go-swagger/go-swagger/cmd/swagger
```

### install from brew
```shell
brew install go-swagger
```

## 準備
GOPATH配下に swagger.yaml を準備します

```shell
mkdir $GOPATH/src/swagger
cd $GOPATH/src/swagger
```

```yaml
swagger: "2.0"
info:
  version: "1.0.0"
  title: "Swagger lob"
  description: "A sample for LOB API"
host: "localhost:8000"
basePath: "/api"
schemes:
  - "http"
consumes:
  - "application/json"
produces:
  - "application/json"
paths:
  /users:
    post:
      operationId: postUser
      parameters:
      - in: body
        name: body
        required: true
        schema:
          $ref: '#/definitions/User'
      responses:
        '201':
          description: Created
  /users/{id}:
    get:
      operationId: getUserById
      parameters:
      - name: "id"
        in: "path"
        required: true
        type: "string"
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/User'
definitions:
  User:
    type: object
    properties:
      id:
        type: number
        format: uint
      name:
        type: string
```
`POST /api/users` で user を作成し
`GET /api/users/{id}` で指定したidのユーザをゲットするシンプルな swagger.yaml です


## サーバサイドのコードの生成
```shell
swagger generate server -f swagger.yaml
```
↑を叩くと以下のようにサーバ側のコードが生成されます

```
.
├── cmd
│   └── swagger-lob-server
│       └── main.go
├── models
│   └── user.go
├── restapi
│   ├── configure_swagger_lob.go
│   ├── doc.go
│   ├── embedded_spec.go
│   ├── operations
│   │   ├── get_user_by_id.go
│   │   ├── get_user_by_id_parameters.go
│   │   ├── get_user_by_id_responses.go
│   │   ├── get_user_by_id_urlbuilder.go
│   │   ├── post_user.go
│   │   ├── post_user_parameters.go
│   │   ├── post_user_responses.go
│   │   ├── post_user_urlbuilder.go
│   │   └── swagger_lob_api.go
│   └── server.go
└── swagger.yaml
```

`models` には　`definitions` で定義された `user` モデルが定義され
`retapi/operations/` にはリクエスト時のパラメータ等を持ったstructや、レスポンス用のstructが定義されています


生成がうまくいかないときは　swagger に誤りがある可能性がありますので
バリデーションをかけて確認しましょう

```shell
swagger validate swagger.yaml
The swagger spec at "swagger.yaml" is valid against swagger specification 2.0
```

## 動かしてみる
この状態で一度動かしてみます
port を指定しないと毎回ランダムで起動してしまいます

```shell
go run cmd/swagger-lob-server/main.go --port=8000
```

```shell
curl http://127.0.0.1:8000/api/users/1
"operation .GetUsersID has not yet been implemented"
```
まだこの時点では、APIの中身を実装していないので、このようなレスポンスが返ってきます


## 実装
APIの中身を実装していきます

`restapi/handler` を作成し、その中にhandlerを作っていきます

```
.
├── cmd
├── models
├── restapi
│   ├── handler
│   │   ├── get_user_by_id.go
│   │   └── post_user.go
│   ├── operations
│   └── server.go
└── swagger.yaml
```

`POST /user` で handler のグローバルな変数にユーザを詰め
`GET /user/{id}` でユーザを取得しレスポンスに詰めて返しています

```post_user.go
package handler

import (
	"github.com/go-openapi/runtime/middleware"
	"swagger/models"
	"swagger/restapi/operations"
)

var (
	Users map[uint64]models.User
)

func init()  {
	Users = map[uint64]models.User{}
}

type PostUserHandler struct {}

func (h *PostUserHandler) Handle(params operations.PostUserParams) middleware.Responder {
	u := models.User{
		ID:params.Body.ID,
		Name: params.Body.Name,
	}

	Users[params.Body.ID] = u

	return operations.NewPostUserCreated()
}
```

```get_user_by_id.go
package handler

import (
	"github.com/go-openapi/runtime/middleware"
	"github.com/golang/go/src/pkg/strconv"
	"swagger/restapi/operations"
)

type GetUserByIDHandler struct {}

func (h *GetUserByIDHandler) Handle(params operations.GetUserByIDParams) middleware.Responder {
	id, _ := strconv.ParseUint(params.ID, 10, 64)

	u := Users[id]

	return operations.NewGetUserByIDOK().WithPayload(&u)
}
```

最後に `configure_swagger_lob.go` に handler を登録します
`go-swagger` から生成された時点では以下のようになっているので

```configure_swagger_lob.go
api.GetUserByIDHandler = operations.GetUserByIDHandlerFunc(func(params operations.GetUserByIDParams) middleware.Responder {
		return middleware.NotImplemented("operation .GetUserByID has not yet been implemented")
	})
	api.PostUserHandler = operations.PostUserHandlerFunc(func(params operations.PostUserParams) middleware.Responder {
		return middleware.NotImplemented("operation .PostUser has not yet been implemented")
	})
```

以下のように更新します

```configure_swagger_lob.go
api.GetUserByIDHandler = operations.GetUserByIDHandlerFunc(func(params operations.GetUserByIDParams) middleware.Responder {
		h := handler.GetUserByIDHandler{}
		return h.Handle(params)
	})
	api.PostUserHandler = operations.PostUserHandlerFunc(func(params operations.PostUserParams) middleware.Responder {
		h := handler.PostUserHandler{}
		return h.Handle(params)
	})
```

## クライアントサイドのコード生成

```
swagger generate client -f swagger.yaml
```

↑を実行すると以下のコードが生成されます

```
.
├── client
│   ├── operations
│   │   ├── get_user_by_id_parameters.go
│   │   ├── get_user_by_id_responses.go
│   │   ├── operations_client.go
│   │   ├── post_user_parameters.go
│   │   └── post_user_responses.go
│   └── swagger_lob_client.go
└── swagger.yaml
```

ここでもリクエストを投げるのに必要なパラメータ等のstructが生成されますので、これらを利用したコードを書きます

```post_user.go
package main

import (
	"log"
	"time"
	"swagger/client/operations"
	"swagger/models"
	apiclient "swagger/client"
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
)

func main() {
	p := &operations.PostUserParams{
		Body: &models.User{
			ID: 1,
			Name: "name_lob",
		},
	}
	p.SetTimeout(10 * time.Second)

	transport := httptransport.New("localhost:8000", "api", nil)
	client := apiclient.New(transport,strfmt.Default)

	res, _ := client.Operations.PostUser(p)

	log.Printf("%#v\n", res.Error())
}
```

```get_uesr_by_id.go
package main

import (
	"log"
	"time"
	"swagger/client/operations"
	apiclient"swagger/client"
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
)

func main()  {
	p := operations.GetUserByIDParams{
		ID: "1",
	}
	p.SetTimeout(10 * time.Second)

	transport := httptransport.New("localhost:8000", "api", nil)
	client := apiclient.New(transport,strfmt.Default)

	res, _ := client.Operations.GetUserByID(&p)

	log.Printf("%+v\n", res)
}
```

`p.SetTimeout(10 * time.Second)`
を指定しないと,タイムアウトになってしまったのでここでは10秒で指定しています


ここでは,パラメータ値が固定ですがCLIツールとして,叩くときに引数でわたしてやれば便利に使えそうです


## 最後に
以上swaggerからgo-swaggerを使って,Goのサーバ,クライアントのコードを生成してAPIを叩いてみるまでを実装してみました
今回は触れていませんが、swaggerではテストやドキュメントの生成等も可能です

実際の開発の現場でも,APIの実装が終了するまではクライアントサイドにswaggerから生成したモックを叩いてもらう.というような使い方もできると思います