###### 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 } ``` ### 實際使用 ![](https://i.imgur.com/fycblfH.png) ![](https://i.imgur.com/5MaiqHQ.png) ```go= // output: // rpc error: code = InvalidArgument desc = {"code":"1-005-03-005","message":"商品參數錯誤","emessage":"item parmeter invalid","details":["Brand: 缺少品牌; Color: 缺少花色; Sale: 錯誤銷售金額."]} ``` {%hackmd theme-dark %}