Go
gin

ginのBindingとValidationについての調査メモ

More than 3 years have passed since last update.

GoのフレームワークginでのBindingとValidationの挙動を調査したので忘れないうちにアウトプットしとく。


環境


  • Mac:OS X Mavericks

  • Go:v1.4.2

  • gin:v1.0rc1


Binding&Validationのやり方

type TestForm struct {

Name string `json:"name" binding:"required"`
Text string `json:"text" binding:"required,max=1000"`
}

まず、Formを格納する構造体を定義してタグにBinding用の定義とValidation用の定義を書く。

Bindingタグの中に実施するValidationを列挙する。複数ある場合はカンマ区切りで書く。

※評価は左から順に行い、Errorがあった場合それ以降の評価は行わない

※利用できるValidationはここを参照

ちなみにここでは、

{"name": "taro", "text": "hogehoge"}

こういうJSON形式で受け取ることを想定している。

※Formで受けたい場合はjsonformに変えればよい。


r.POST("/test", func(c *gin.Context) {

var form TestForm
c.Bind(&form)

log.Println(form.Name) // taro
log.Println(form.Text) // hogehoge
...

次にHandlerFuncの中で定義したFormの構造体を宣言して、gin.ContextBindメソッドにポインタを与えると良しなにBindingしてくれる。


Validationエラーのハンドリング

このままだとValidationエラーになっていてもスルーしてFormには初期値が入った状態で処理が進んでしまうので、ハンドリング処理を書く。

r.POST("/test", func(c *gin.Context) {

var form TestForm
if err := c.Bind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "BadRequest"})
return
}

// 以下、正常処理

c.Bindは戻り値にerrorを返すので、それを拾ってあげれば良い。


errorの詳細を確認

Validationエラーの詳細をログに出したりResponseに含めたい場合もあると思うので、そのやり方。


単純なロギング

r.POST("/test", func(c *gin.Context) {

var form TestForm
if err := c.Bind(&form); err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"status": "BadRequest"})
return
}

// 以下、正常処理

c.Bindの戻り値errをそのままログに出してみると、

Struct: TestForm

Field validation for "Name" failed on the "required" tag
Field validation for "Text" failed on the "required" tag

こんな感じのログが出力された。

ただロギングしたいだけならこれで十分かも知れない。


errorの詳細

次にerrの中身を確認してみる。

r.POST("/test", func(c *gin.Context) {

var form TestForm
if err := c.Bind(&form); err != nil {
errors := err.(*validator.StructErrors)
log.Println("Struct:", errors.Struct)
for k, v := range errors.Errors {
log.Println("Key:", k)
log.Println("Field:", v.Field)
log.Println("Param:", v.Param)
log.Println("Tag:", v.Tag)
log.Println("Kind", v.Kind)
log.Println("Type:", v.Type)
log.Println("Value", v.Value)
log.Println("==========")
}
log.Println(errors.StructErrors)
c.JSON(http.StatusBadRequest, gin.H{"status": "BadRequest"})
return
}

// 以下、正常処理

ginのValidationは内部ではgopkg.in/bluesuncorp/validator(gin:v1.0rc1ではv5)を使っており、c.Bindの戻り値も同パッケージのStructErrors構造体を返している。


StructErrors

type StructErrors struct {

Struct string
Errors map[string]*FieldError
StructErrors map[string]*StructErrors
}


FieldError

type FieldError struct {

Field string
Tag string
Kind reflect.Kind
Type reflect.Type
Param string
Value interface{}
}

StructErrors構造体とFieldErrorはこのような構成になっている。

とりあえずStructErrorsの中身をひと通りログに出してみると、

Struct: TestForm

key: Name
Field: Name
Param:
Tag: required
Kind string
Type: string
Value
==========
key: Text
Field: Text
Param: 10
Tag: max
Kind string
Type: string
Value hogehogehoge
==========
StructErrors: map[]

こんな感じ。

要約すると以下のような内容。

要素
内容

StructErrors.Struct
Validation対象の構造体名

StructErrors.Errors
ErrorになったFieldを保持したMap(Key:Field名、Value:FieldError)

StructErrors.StructErrors
Formが構造体の入れ子になっている場合、入れ子の構造体に対するValidationの結果が格納されるMap(Key:構造体名、Value:StructErrors)

FieldError.Field
ErrorになったField名

FieldError.Tag
Errorになったタグ名

FieldError.Kind
ErrorになったFieldのKind

FieldError.Type
ErrorになったFieldのType

FieldError.Param
ErrorになったFieldのValidationに定義した値

FieldError.Value
ErrorになったFieldに与えられた値


入れ子になったForm構造体のValidation

Formの構造体が入れ子になっていてもValidationを適用できる。

type BaseForm struct {

ID int64 `json:"id" binding:"required"`
}

type TestForm struct {
Name string `json:"name" binding:"required"`
Text string `json:"text" binding:"required,max=1000"`
Base BaseForm `json:"base"`
}

このようにFormの構造体を作り、

{

"name": "hoge",
"text": "hogehoge",
"base": {
"id": 123456
}
}

こんな形式でリクエストしてやればOK。

これでBaseFormのidもValidationも実施してくれる。


とりあえずここまで

gopkg.in/bluesuncorp/validatorはValidationの種類も充実していて良さ気。必要なら独自Validationも実装できるみたいなので、次回はその辺も調べてみたいと思う。