前のページ
Featured image of post gin で入れ子の validation が効かない時

gin で入れ子の validation が効かない時

結論から

まず結論から話すと、 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 を使いましょう。

ちなみに、公式のドキュメントあります。

最初から公式を読みましょう!(と、言いつつググった方が早い時もあるよね)

このページでは、Google Analyticsを利用しています。