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で受けたい場合はjson
をform
に変えればよい。
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.Context
のBind
メソッドにポインタを与えると良しなに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
構造体を返している。
type StructErrors struct {
Struct string
Errors map[string]*FieldError
StructErrors map[string]*StructErrors
}
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も実装できるみたいなので、次回はその辺も調べてみたいと思う。