結論から
まず結論から話すと、 dive
tag を使えば良いです。
入れこのときに validation が効かない?
golang で api 開発をしているときによく使われる web フレームワークとして Gin
があります
gin は api の入り口/出口として router や request/response 、validation など(こんなもんじゃないけど)とても高機能なフレームワークです
ただ golang を書きはじめたときや、ふと忘れてしまう処理として、入れ子構造の場合の validation の設定方法です
具体的には以下のような構造体と validation が設定されていると想定します
type User struct {
Name string `json:"name"`
Friends []Friend `json:"friends"`
}
type Friend struct {
Name string `json:"name" binding:"required"`
Phone string `json:"phone"`
}
上記のような構造体をリクエストとして受け取る API の場合、このような json が渡されたらどうなるでしょうか?
{
"name": "hoge",
"friends": [
{
"phone": "000-0000-0000"
}
]
}
直感的に考えれば、 friends.name が json に書かれておらず、 Friend
構造体の Name
field の binding に設定されている required tag が聞くために validation に引っかかりそうではないですか?
少なくとも私はそう考えて、api を開発したことがあります。テストを作る段階や、いろんなパラメータで試しにリクエストを叩いたときに、ふとおかしなことに気づきます。上記のような json を渡しても validation のエラーが吐かれていないのです。
gin と binding のサンプルコード
とても簡易的ではありますが、試しに以下のコードでサーバーを起動してリクエストを叩いてみます
失敗すれば、error
のキーを持つ json が返され、成功すれば、bind した構造体が返されます。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/no_dive", noDive)
r.Run()
}
type NoDiveUser struct {
Name string `json:"name"`
Friends []Friend `json:"friends"`
}
type Friend struct {
Name string `json:"name" binding:"required"`
Phone string `json:"phone"`
}
func noDive(ctx *gin.Context) {
u := &NoDiveUser{}
if err := ctx.ShouldBindJSON(u); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, *u)
}
gin を使いはじめて初期の頃は、先ほどの構造体(User だけど、以下サンプルコードでは NoDiveUser )に対して、friends.name が設定されていないリクエストを叩いたとしたらエラーメッセージが返ってくると思っていました。
リクエストは curl でも postman でも何でも良いですが、一旦 curl で叩いてみます
curl --location --request GET 'localhost:8080/no_dive' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "hoge",
"friends": [
{
"phone": "000-0000-0000"
}
]
}' | jq
結果
{
"name": "hoge",
"friends": [
{
"name": "",
"phone": "000-0000-0000"
}
]
}
構造体が返ってきていますね…
ふむー。どうやら先ほどまでの理解は間違っていたようです
slice(入れ子)の場合には dive tag を使う
gin validate slice
などでググっていると(というか公式を読めよという話ではある)、以下の記事を見つけました。
https://github.com/gin-gonic/gin/issues/1041
gin の issues に投稿されていました
その中で以下のコメントがありました
you have to use
dive
tag that tells the validator to dive into a slice, array or map.
なるほど。求めていた情報を見つけました。
gin で slice の validation をするサンプルコード
先ほどと異なるのは、User の構造体の Friends fields に binding:"dive"
の tag をつけたことです
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/dive", dive)
r.Run()
}
type User struct {
Name string `json:"name"`
Friends []Friend `json:"friends" binding:"dive"`
}
type Friend struct {
Name string `json:"name" binding:"required"`
Phone string `json:"phone"`
}
func dive(ctx *gin.Context) {
u := &User{}
if err := ctx.ShouldBindJSON(u); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, *u)
}
さて、先ほどと同じ json request を叩いてみましょう
curl --location --request GET 'localhost:8080/dive' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "hoge",
"friends": [
{
"phone": "000-0000-0000"
}
]
}' | jq
結果
{
"error": "Key: 'User.Friends[0].Name' Error:Field validation for 'Name' failed on the 'required' tag"
}
これです。求めていたやつです。
まとめ
gin の入れ子の validation は dive
tag を使いましょう。
ちなみに、公式のドキュメントあります。
最初から公式を読みましょう!(と、言いつつググった方が早い時もあるよね)