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

[Go](https://github.com/golang/go)のフレームワーク[gin](https://github.com/gin-gonic/gin)でのBindingとValidationの挙動を調査したので忘れないうちにアウトプットしとく。

# 環境
* Mac:OS X Mavericks
* Go:v1.4.2
* gin:v1.0rc1

# Binding&Validationのやり方
```go
type TestForm struct {
    Name string `json:"name" binding:"required"`
    Text string `json:"text" binding:"required,max=1000"`
}
```
まず、Formを格納する構造体を定義してタグにBinding用の定義とValidation用の定義を書く。
Bindingタグの中に実施するValidationを列挙する。複数ある場合はカンマ区切りで書く。
**※評価は左から順に行い、Errorがあった場合それ以降の評価は行わない**
**※利用できるValidationは[ここ](http://godoc.org/gopkg.in/bluesuncorp/validator.v5#hdr-Baked_In_Validators_and_Tags)を参照**

ちなみにここでは、

```
{"name": "taro", "text": "hogehoge"}
```
こういうJSON形式で受け取ることを想定している。
※Formで受けたい場合は`json`を`form`に変えればよい。

----

```go
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には初期値が入った状態で処理が進んでしまうので、ハンドリング処理を書く。

```go
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に含めたい場合もあると思うので、そのやり方。

## 単純なロギング

```go
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の中身を確認してみる。

```go
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](http://gopkg.in/bluesuncorp/validator.v5)）を使っており、`c.Bind`の戻り値も同パッケージの`StructErrors`構造体を返している。

```go:StructErrors
type StructErrors struct {
    Struct string
    Errors map[string]*FieldError
    StructErrors map[string]*StructErrors
}
```
```go: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を適用できる。

```go
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も実装できるみたいなので、次回はその辺も調べてみたいと思う。