仕様互換を仕上げる
同じ仕様を、別の言語で満たす
第9章でやるのは、実務でよくある仕事です。動いている API を、別の言語で作り直す。
作り直すとき、中身は自由に書き換えられます。ですが外から見た振る舞いは1ミリも変えられません。使っている側は API の内側を知らないので、応答が少しでも違えば壊れます。
プレーンテキスト
変えてよい 使う言語、内部の構造、性能
変えられない URL、メソッド、応答の形、ステータスコード仕様書が正
fleama API の仕様は次のとおりです。
プレーンテキスト
GET /items 200 {"total":N,"items":[...]}
GET /items/{id} 200 {"id":..,"name":..,"price":..}
404 {"error":"not found"}
400 {"error":"invalid id"}
POST /items 201 作成した商品
400 {"error":"理由"}
それ以外のパス 404 {"error":"not found"}第8章で作ったものと1か所だけ違います。一覧が配列そのままではなく、total で包まれた形です。
プレーンテキスト
第8章 [{...},{...}]
仕様書 {"total":2,"items":[{...},{...}]}第8章で「どちらが正しいということはない、ただし揃えること」と書きました。仕様書がある以上、こちらに合わせます。好みは関係ありません。
包む形を作る
専用の構造体を用意するのが素直です。
Go
type ListResponse struct {
Total int `json:"total"`
Items []Item `json:"items"`
}応答の形そのものを型にしておくと、キー名の付け忘れが起こりません。
ここでも Items を nil のままにしないでください。0件のときに "items":null になります。仕様は [] です。
登録していないパスも揃える
第8章で触れたとおり、ServeMux の既定の 404 は page not found という文言です。仕様は JSON なので、これも揃えます。
Go
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
writeError(w, 404, "not found")
})/ は前方一致なので、どのパスにも当てはまらなかったものがここへ来ます。より具体的な登録が優先されるので、/items などは奪われません。
互換かどうかを測る
「たぶん同じ」では作り直しになりません。同じリクエストを両方に投げて、応答が1文字単位で一致するかを確かめます。
この演習のテストが、その突合そのものです。仕様書に書かれた応答を期待値に置いてあります。
要件
- 一覧の応答を
{"total":N,"items":[...]}の形にする - 0件でも
"items":[]を返す - 登録していないパスに
404 {"error":"not found"}を返す - 詳細と出品の振る舞いは変えない
入出力例
callAPI("GET", "/items", "") → "200 {"total":2,"items":[{"id":1,"name":"きのこ図鑑","price":1200},{"id":2,"name":"中古スマホ","price":24800}]}"
callAPI("GET", "/items?keyword=中古スマホ", "") → "200 {"total":1,"items":[{"id":2,"name":"中古スマホ","price":24800}]}"
callAPI("GET", "/items?keyword=謎の箱", "") → "200 {"total":0,"items":[]}"
callAPI("GET", "/items/1", "") → "200 {"id":1,"name":"きのこ図鑑","price":1200}"
callAPI("GET", "/unknown", "") → "404 {"error":"not found"}"
callAPI("POST", "/items", "{"name":"新商品","price":500}") → "201 {"id":0,"name":"新商品","price":500}"ヒント
編集 ゆめさく編集部