92
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-06-29

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

92
72
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
92
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?