swaggerの定義ファイルからgo-swaggerを使ってGoで書かれたサーバとクライアントのコードを生成して,実際にAPIを叩いてみようと思います
公式ドキュメントはこちら
swagger とは
swagger とは RESTful API の仕様を定義したオープンソースのフレームワークです
マイクロソフト, Google, IBM などが参加する 「OPEN API Initiative」 という団体が The Linux Foundation の協力のもと推進しています
サーバ, クライアントのコード以外にもテストやドキュメントの生成もすることができます
インストール
install from source
go get -u github.com/go-swagger/go-swagger/cmd/swagger
install from brew
brew install go-swagger
準備
GOPATH配下に swagger.yaml を準備します
mkdir $GOPATH/src/swagger
cd $GOPATH/src/swagger
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 です
サーバサイドのコードの生成
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 に誤りがある可能性がありますので
バリデーションをかけて確認しましょう
swagger validate swagger.yaml
The swagger spec at "swagger.yaml" is valid against swagger specification 2.0
動かしてみる
この状態で一度動かしてみます
port を指定しないと毎回ランダムで起動してしまいます
go run cmd/swagger-lob-server/main.go --port=8000
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}
でユーザを取得しレスポンスに詰めて返しています
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()
}
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
から生成された時点では以下のようになっているので
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")
})
以下のように更新します
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が生成されますので、これらを利用したコードを書きます
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())
}
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から生成したモックを叩いてもらう.というような使い方もできると思います