###### tags: `Golang Package`
# ozzo-validation
[github](https://github.com/go-ozzo/ozzo-validation)
## Intro
主要提供數據校驗的功能
* 使用普通的程式結構,而不是容易出錯的結構標記來指定應如何驗證數據。
* 可以驗證不同類型的數據,例如structs, strings, byte slices, slices, maps, arrays。
* 可以驗證自定義數據類型。
* 可定制且格式正確的驗證錯誤。
* 非常容易創建和使用自定義驗證規則。
### 簡單的驗證值
``` go=
data := "example"
err := validation.Validate(data,
validation.Required, // 不可為空
validation.Length(5, 100), // 5~10個字
)
fmt.Println(err)
// Output:
// <nil>
```
```go=
data := "example"
err := validation.Validate(data,
validation.Required, // 不可為空
validation.Length(5, 100), // 5~10個字
)
fmt.Println(err)
// Output:
// must be a valid URL
```
### 驗證結構
```go=
func (a Address) Validate() error {
return validation.ValidateStruct(&a,
// Street不可為空、長度須為5~50字
validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
// City不可為空、長度須為5~50字
validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
// State不可為空、需有兩的大寫字母
validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
// Zip不可為空、需有5個數字
validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
)
}
a := Address{
Street: "123",
City: "Unknown",
State: "Virginia",
Zip: "12345",
}
err := a.Validate()
fmt.Println(err)
// Output:
// Street: the length must be between 5 and 50;
// State: must be in a valid format.
```
### 驗證Map
需要知道欄位才可做檢查
```go=
c := map[string]interface{}{
"Name": "Qiang Xue",
"Email": "q",
"Address": map[string]interface{}{
"Street": "123",
"City": "Unknown",
"State": "Virginia",
"Zip": "12345",
},
}
err := validation.Validate(c,
validation.Map(
validation.Key("Name", validation.Required, validation.Length(5, 20)),
validation.Key("Email", validation.Required, is.Email),
validation.Key("Address", validation.Map(
validation.Key("Street", validation.Required, validation.Length(5, 50)),
validation.Key("City", validation.Required, validation.Length(5, 50)),
// 不可為空,需有兩個大寫字母
validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
// 不可為空,需有五個數字
validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
)),
),
)
```
### 自訂錯誤顯示
若使用預設的檢查函式,能自訂的最多是欄位名稱
```go=
c := Customer{
Name: "Qiang Xue",
Email: "q",
Address: Address{
State: "Virginia",
},
}
err := validation.Errors{
"name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)),
"email": validation.Validate(c.Name, validation.Required, is.Email),
"zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
}.Filter()
fmt.Println(err)
// Output:
// email: must be a valid email address; zip: cannot be blank.
```
### 實現接口
如果子結構有實現`validate`介面,呼叫`ValidateStruct`時會自動使用
```go=
func main() {
c := &Customer{
Name: "Qiang Xue",
Email: "a1008023@gmail.com",
Address: &Address{
State: "Virginia",
},
}
err := c.Validate()
fmt.Println(err)
}
type Customer struct {
Name string
Email string
Address *Address
}
func (c *Customer) Validate() error {
return validation.ValidateStruct(c,
validation.Field(&c.Email, is.Email),
validation.Field(&c.Name, validation.Length(5, 20)),
validation.Field(&c.Address),
)
}
type Address struct {
State string
Zip string
}
func (a *Address) Validate() error {
return validation.ValidateStruct(a,
validation.Field(&a.State, validation.Required),
validation.Field(&a.Zip, validation.Required),
)
}
```
需注意在實現介面時,不要對原始類型的欄位調用Validate,建議是直接轉成該原始類型後再進行檢查。
```go=
func main() {
a := new(Mstr)
*a = "A"
m := Mystring{
Str: a,
}
err := m.Validate()
fmt.Println(err)
}
type Mystring struct {
Str *Mstr
}
type Mstr string
func (c *Mstr) Validate() error {
spew.Dump("1")
return validation.Validate(&c)
}
func (m *Mystring) Validate() error {
return validation.ValidateStruct(m,
validation.Field(&m.Str),
)
}
```
Map、Array、Slice也都可以直接調用Validate
```go=
addresses := []Address{
Address{State: "MD", Zip: "12345"},
Address{Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"},
Address{City: "Unknown", State: "NC", Zip: "123"},
}
err := validation.Validate(addresses)
// Output:
// 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.).
```
### Each
用來逐一檢查陣列元素,但即便沒有使用Each,陣列也會默認檢查全部,主要差別在於error會列出第幾個元素出錯
```go=
func main (){
c := &Customer{
Name: []string{"Qiang Xue", "A"},
Email: "a1008023@gmail.com w",
Address1: &Address1{
State: "Virginia",
},
}
err := c.Validate()
// output:
// 有Each -> Name: (1: the length must be between 5 and 20.)
// 沒Each -> Name: the length must be between 5 and 20
}
func (c *Customer) Validate() error {
return validation.ValidateStruct(c,
validation.Field(&c.Email, is.Email),
validation.Field(&c.Name, validation.Each(validation.Length(5, 20))),
//validation.Field(&c.Name, validation.Length(5, 20)),
validation.Field(&c.Address1, validation.Required),
)
}
```
### 條件式驗證
```go=
result := validation.ValidateStruct(&a,
// 當Quantity不為空時,Unit必填;當Quantity為空時,Unit需為空
validation.Field(&a.Unit, validation.Required.When(a.Quantity != ""), validation.Nil.When(a.Quantity == "")),
// 與上同義,不同寫法
validation.Field(&a.Unit, validation.When(a.Quantity != "", validation.Required).Else(validation.Nil)),
validation.Field(&a.Phone, validation.Required.When(a.Email == "").Error('Either phone or Email is required.')),
validation.Field(&a.Email, validation.Required.When(a.Phone == "").Error('Either phone or Email is required.')),
)
```
### 自訂錯誤訊息
```go=
func (c *Customer) Validate() error {
return validation.ValidateStruct(c,
validation.Field(&c.Email, is.Email.Error(status.InvalidParameter.WithDetail([]string{"電子郵件格式錯誤"}...).Error())),
validation.Field(&c.Name, validation.Each(validation.Length(5, 20).Error(status.InvalidParameter.WithDetail([]string{"名字長度為5~20字"}...).Error()))),
)
}
// output:
// Email: rpc error: code = InvalidArgument desc = {"code":"1-001-03-005","message":"錯誤的參數","emessage":"invalid parameter","details":["電子郵件格式錯誤"]}; Name: (1: rpc error: code = InvalidArgument desc = {"code":"1-001-03-005","message":"錯誤的參數","emessage":"invalid parameter","details":["名字長度為5~20字"]}.).
```
### 自訂義規則
```go=
func main() {
err := validation.Validate("xyz", validation.By(checkWord))
fmt.Println(err)
// output:
// rpc error: code = InvalidArgument desc = {"code":"1-001-03-005","message":"錯誤的參數","emessage":"invalid parameter"}
}
func checkWord(v interface{}) error {
if v != "abc" {
return status.InvalidParameter.Err()
}
return nil
}
```
### 實際使用


```go=
// output:
// rpc error: code = InvalidArgument desc = {"code":"1-005-03-005","message":"商品參數錯誤","emessage":"item parmeter invalid","details":["Brand: 缺少品牌; Color: 缺少花色; Sale: 錯誤銷售金額."]}
```
{%hackmd theme-dark %}