validatorの *csfield タグはArray, Slice, Mapのlenを比較したい時につける

## この投稿で使用している言語、ライブラリのバージョン

* Go v1.17.7
* [github.com/go-playground/validator](https://github.com/go-playground/validator) v10.10.0

## 疑問に思ったこと

validatorのタグには、`gtecsfield`と`gtefield`や、`ltcsfield`と`ltfield`といった`cs`付きとそうでないものがあります。

この違いは何か？

例えば `gtecsfield`/`gtefield`は以下のような説明があります。

| Tag | Description |
|:-----------|:------------|
| gtecsfield | Field Greater Than or Equal To Another Relative Field |
| gtefield | Field Greater Than or Equal To Another Field |

この説明を読んで、ある構造体Aが別の構造体Bを持っていて、AのフィールドとBのフィールドとを比較したい場合に *csfieldタグ を使用するのかな？と思っていました。

例えばこんな感じ

```go
type Event struct {
	Name         string
	StartDate    time.Time `validate:"gtecsfield=Notification.NotificationTime"`
	Notification Notification
}

type Notification struct {
	NotificationTime time.Time
}
```

でもこのコードは`StartDate`のタグを`gtecsfield`から`gtefield`に変更しても問題なく動きます。

```go
package main

import (
	"fmt"
	"time"

	"github.com/go-playground/validator/v10"
)

type Event struct {
	Name         string
	StartDate    time.Time `validate:"gtefield=Notification.NotificationTime"` // <= `gtecsfield`ではなく`gtefield`を使う
	Notification Notification
}

type Notification struct {
	NotificationTime time.Time
}

func main() {
	validate := validator.New()
	now := time.Now()

	validEvent := Event{
		Name:      "EventName1",
		StartDate: now,
		Notification: Notification{
			NotificationTime: now.AddDate(0, 0, -1),
		},
	}

	if err := validate.Struct(validEvent); err != nil {
		errs := err.(validator.ValidationErrors)
		for _, errrs := range errs {
			fmt.Printf("validEvent errs=%+v\n", errrs)
		}
	}

	invalidEvent := Event{
		Name:      "EventName1",
		StartDate: now,
		Notification: Notification{
			NotificationTime: now.AddDate(0, 0, 1),
		},
	}

	if err := validate.Struct(invalidEvent); err != nil {
		errs := err.(validator.ValidationErrors)
		for _, errrs := range errs {
			fmt.Printf("invalidEvent errs=%+v\n", errrs)
		}
	}
}

```

```
$ go run main.go
invalidEvent errs=Key: 'Event.StartDate' Error:Field validation for 'StartDate' failed on the 'gtefield' tag
```

じゃあ `*csfield` ってどこで使うの？

## 該当箇所のソースコード

`gtecsfield`と`gtefield`のそれぞれのタグを使用した場合に呼ばれる関数はそれぞれ以下の通りです。

[gtecsfieldタグを使った場合に呼ばれる関数](https://github.com/go-playground/validator/blob/master/baked_in.go#L939)

[gtefieldタグを使った場合に呼ばれる関数](https://github.com/go-playground/validator/blob/master/baked_in.go#L1652)

ローカル変数が若干異なることを除き、違いは一箇所だけで `*csfield` は Array, Slice, Map の場合にlenで比較しています。

[gtecsfieldタグはArray, Slice, Mapの場合lenで比較している](https://github.com/go-playground/validator/blob/master/baked_in.go#L960)

[gtefieldタグはArray, Slice, Mapの判定をしていない](https://github.com/go-playground/validator/blob/master/baked_in.go#L1675)

試してみると確かに`gtefield`ではSliceのサイズでのバリデーションができませんでした。

```go:main.go
package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

type Hoge struct {
	Items1 []string `validate:"gtecsfield=Items2"`
	Items2 []string
}

type Fuga struct {
	Items1 []string `validate:"gtefield=Items2"`
	Items2 []string
}

func main() {
	validate := validator.New()

	hoge := Hoge{
		Items1: []string{"A", "B"},
		Items2: []string{"A", "B", "C"},
	}

	if err := validate.Struct(hoge); err != nil {
		errs := err.(validator.ValidationErrors)
		for _, errrs := range errs {
			fmt.Printf("hoge errs=%+v\n", errrs)
		}
	}

	fuga := Fuga{
		Items1: []string{"A", "B"},
		Items2: []string{"A", "B", "C"},
	}

	if err := validate.Struct(fuga); err != nil {
		errs := err.(validator.ValidationErrors)
		for _, errrs := range errs {
			fmt.Printf("fuga errs=%+v\n", errrs) // Items2でバリデーション違反になってほしいがならない
		}
	}
}

```

```
$ go run main.go
hoge errs=Key: 'Hoge.Items1' Error:Field validation for 'Items1' failed on the 'gtecsfield' tag
```

## まとめ

`gtecsfield`や`ltcsfield`といった`cs`付きのタグは Array, Slice, Map の場合にlenを見てくれるのが違い。

基本的には `cs` 付きのタグを使っておけば良さそう。