### 描述
ozzo-validation是一個Go軟件包,提供可配置和可擴展的數據驗證功能。它具有以下功能:
+ 使用常規的編程構造而不是容易出錯的構造標記來指定應如何驗證數據。
+ 可以驗證不同類型的數據,例如結構,字符串,字節片,片,映射,數組。
+ 只要實現Validatable接口,就可以驗證自定義數據類型。
+ 可以驗證實現該sql.Valuer接口的數據類型(例如sql.NullString)。
+ 可自定義且格式正確的驗證錯誤。
+ 立即提供豐富的驗證規則集。
+ 創建和使用自定義驗證規則非常容易。
+ github地址: https://github.com/go-ozzo/ozzo-validation
### 要求
達到1.13或更高。
### 入門
ozzo驗證包主要包括一組驗證規則和兩種驗證方法。您使用驗證規則來描述應如何將值視為有效,然后調用validation.Validate() 或validation.ValidateStruct()驗證值。
### 安裝
運行以下命令以安裝軟件包:
```
go get github.com/go-ozzo/ozzo-validation/v3
go get github.com/go-ozzo/ozzo-validation/v3/is
```
### 驗證簡單值
對于簡單的值(例如字符串或整數),可以使用validation.Validate()它進行驗證。例如,
```go
package main
import (
"fmt"
"github.com/go-ozzo/ozzo-validation/v3"
"github.com/go-ozzo/ozzo-validation/v3/is"
)
func main() {
data := "example"
err := validation.Validate(data,
validation.Required, // not empty
validation.Length(5, 100), // length between 5 and 100
is.URL, // is a valid URL
)
fmt.Println(err)
// Output:
// must be a valid URL
}
```
該方法validation.Validate()將按照列出的順序運行規則。如果規則未能通過驗證,則該方法將返回相應的錯誤,并跳過其余規則。如果值通過所有驗證規則,則該方法將返回nil。
### 驗證結構
對于結構值,通常需要檢查其字段是否有效。例如,在RESTful應用程序中,您可以將請求有效內容解組為結構,然后驗證結構字段。如果一個或多個字段無效,則可能需要獲取描述哪些字段無效的錯誤。您可以使用validation.ValidateStruct() 以實現此目的。單個結構可以具有多個字段的規則,并且一個字段可以與多個規則關聯。例如,
```go
package main
import (
"fmt"
"regexp"
"github.com/go-ozzo/ozzo-validation/v3"
"github.com/go-ozzo/ozzo-validation/v3/is"
)
type Address struct {
Street string
City string
State string
Zip string
}
func (a Address) Validate() error {
return validation.ValidateStruct(&a,
// Street cannot be empty, and the length must between 5 and 50
validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
// City cannot be empty, and the length must between 5 and 50
validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
// State cannot be empty, and must be a string consisting of two letters in upper case
validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
// State cannot be empty, and must be a string consisting of five digits
validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
)
}
func main() {
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.
}
```
請注意,在調用validation.ValidateStruct以驗證結構時,應將指向結構的指針(而不是結構本身)傳遞給方法。同樣,在調用validation.Field以指定結構字段的規則時,應使用指向結構字段的指針。
執行結構驗證時,將按照在中指定的順序驗證字段ValidateStruct。并且,當每個字段都經過驗證時,其規則也會按照與該字段關聯的順序進行評估。如果規則失敗,則會為該字段記錄一個錯誤,并且驗證將在下一個字段繼續。
### 驗證錯誤
該validation.ValidateStruct方法返回在結構字段中發現的驗證錯誤,這些驗證錯誤validation.Errors 是字段及其對應錯誤的映射。如果驗證通過,則返回Nil。
默認情況下,validation.Errors使用命名的struct標記json來確定應使用哪些名稱來表示無效字段。該類型還實現了json.Marshaler接口,以便可以將其編組為適當的JSON對象。例如,
```go
type Address struct {
Street string `json:"street"`
City string `json:"city"`
State string `json:"state"`
Zip string `json:"zip"`
}
// ...perform validation here...
err := a.Validate()
b, _ := json.Marshal(err)
fmt.Println(string(b))
// Output:
// {"street":"the length must be between 5 and 50","state":"must be in a valid format"}
```
您可以修改validation.ErrorTag以使用其他結構標記名稱。
如果您不喜歡ValidateStruct根據結構字段名稱或相應的標記值確定錯誤鍵的魔術,則可以使用以下替代方法:
```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.
```
在上面的示例中,我們validation.Errors通過名稱列表和相應的驗證結果來構建。最后,我們要求Errors.Filter()從Errors所有與成功驗證結果相對應的nil中刪除。如果Errors為空,該方法將返回nil 。
上面的方法非常靈活,因為它允許您自由地建立驗證錯誤結構。您可以使用它來驗證結構和非結構值。與用于ValidateStruct驗證結構相比,它的缺點是您必須冗余地指定錯誤密鑰,同時ValidateStruct可以自動找出它們。
### 內部錯誤
內部錯誤與驗證錯誤的不同之處在于,內部錯誤是由代碼故障(例如,驗證器在遠程服務中斷時進行遠程調用以驗證某些數據)引起的,而不是由正在驗證的數據引起的。在數據驗證期間發生內部錯誤時,您可以允許用戶重新提交相同的數據以再次執行驗證,以希望程序恢復運行。另一方面,如果由于數據錯誤導致數據驗證失敗,則用戶通常不應再次重新提交相同的數據。
為了將內部錯誤與驗證錯誤區分開,當驗證程序中發生內部錯誤時,請validation.InternalError通過調用將其包裝validation.NewInternalError()。然后,驗證者的用戶可以檢查返回的錯誤是否是內部錯誤。例如,
```go
if err := a.Validate(); err != nil {
if e, ok := err.(validation.InternalError); ok {
// an internal error happened
fmt.Println(e.InternalError())
}
}
```
### 可驗證的類型
如果類型實現了validation.Validatable接口,則該類型是有效的。
當validation.Validate用于驗證有效值時,如果在給定的驗證規則中未發現任何錯誤,它將進一步調用該值的Validate()方法。
同樣,在validation.ValidateStruct驗證類型可驗證的結構字段時,它將Validate在通過列出的規則后調用該字段的方法。
> 注意:在實現時validation.Validatable,請勿調用validation.Validate()驗證其原始類型的值,因為這將導致無限循環。例如,如果你定義一個新類型MyString作為string 和實施validation.Validatable為MyString,在內部Validate()功能,你應該值轉換為string調用之前先validation.Validate()對其進行驗證。
在以下示例中,由于實現 ,的Address字段Customer是有效的。因此,在使用驗證結構時,驗證將“潛入”該領域。Addressvalidation.ValidatableCustomervalidation.ValidateStructAddress
```go
type Customer struct {
Name string
Gender string
Email string
Address Address
}
func (c Customer) Validate() error {
return validation.ValidateStruct(&c,
// Name cannot be empty, and the length must be between 5 and 20.
validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
// Gender is optional, and should be either "Female" or "Male".
validation.Field(&c.Gender, validation.In("Female", "Male")),
// Email cannot be empty and should be in a valid email format.
validation.Field(&c.Email, validation.Required, is.Email),
// Validate Address using its own validation rules
validation.Field(&c.Address),
)
}
c := Customer{
Name: "Qiang Xue",
Email: "q",
Address: Address{
Street: "123 Main Street",
City: "Unknown",
State: "Virginia",
Zip: "12345",
},
}
err := c.Validate()
fmt.Println(err)
// Output:
// Address: (State: must be in a valid format.); Email: must be a valid email address.
```
有時,您可能要跳過對類型Validate方法的調用。為此,只需將validation.Skip規則與要驗證的值相關聯即可。
### Maps/Slices/Arrays 可驗證
驗證其元素類型實現validation.Validatable接口的可迭代(映射,切片或數組)時,該validation.Validate方法將調用Validate每個非null元素的方法。將返回元素的驗證錯誤,因為validation.Errors它將無效元素的鍵映射到其相應的驗證錯誤。例如,
```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)
fmt.Println(err)
// Output:
// 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.).
```
當validation.ValidateStruct用于驗證結構時,以上驗證過程也適用于那些結構字段,這些結構字段是可驗證對象的映射/切片/數組。
Each
另一種選擇是使用該validation.Each方法。此方法允許您為結構中的可迭代對象定義規則。
```go
type Customer struct {
Name string
Emails []string
}
func (c Customer) Validate() error {
return validation.ValidateStruct(&c,
// Name cannot be empty, and the length must be between 5 and 20.
validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
// Emails are optional, but if given must be valid.
validation.Field(&c.Emails, validation.Each(is.Email)),
)
}
c := Customer{
Name: "Qiang Xue",
Emails: []Email{
"valid@example.com",
"invalid",
},
}
err := c.Validate()
fmt.Println(err)
// Output:
// Emails: (1: must be a valid email address.).
```
### 指針
當要驗證的值是指針時,大多數驗證規則將驗證指針指向的實際值。如果指針為零,則這些規則將跳過驗證。
validation.Required和validation.NotNil規則是一個例外。當指針為零時,它們將報告驗證錯誤。
### 類型實施 sql.Valuer
如果數據類型實現了sql.Valuer接口(例如sql.NullString),則內置的驗證規則將正確處理該接口。特別是,當規則驗證此類數據時,它將調用該Value()方法并驗證返回的值。
### 必需vs.非零
驗證輸入值時,有兩種有關檢查是否提供輸入值的方案。
在第一種情況下,如果未輸入輸入值或將其輸入為零值(例如,空字符串,零整數),則認為輸入值丟失。validation.Required在這種情況下,您可以使用規則。
在第二種情況下,僅當未輸入輸入值時,才將其視為丟失。通常在這種情況下使用指針字段,以便您可以通過檢查指針是否為nil來檢測是否輸入了值。您可以使用validation.NotNil規則來確保輸入值(即使它是零值)。
### 嵌入式結構
該validation.ValidateStruct方法將正確驗證包含嵌入式結構的結構。特別是,將嵌入式結構的字段視為直接屬于包含的結構。例如,
```go
type Employee struct {
Name string
}
func ()
type Manager struct {
Employee
Level int
}
m := Manager{}
err := validation.ValidateStruct(&m,
validation.Field(&m.Name, validation.Required),
validation.Field(&m.Level, validation.Required),
)
fmt.Println(err)
// Output:
// Level: cannot be blank; Name: cannot be blank.
```
在上面的代碼中,我們用于&m.Name指定Name嵌入式struct 的字段的驗證Employee。驗證錯誤Name用作與該Name字段關聯的錯誤的鍵,就好像Name一個字段直接屬于Manager。
如果Employee實現該validation.Validatable接口,我們還可以使用以下代碼來驗證 Manager,它會生成相同的驗證結果:
```go
func (e Employee) Validate() error {
return validation.ValidateStruct(&e,
validation.Field(&e.Name, validation.Required),
)
}
err := validation.ValidateStruct(&m,
validation.Field(&m.Employee),
validation.Field(&m.Level, validation.Required),
)
fmt.Println(err)
// Output:
// Level: cannot be blank; Name: cannot be blank.
```
### 內置驗證規則
validation軟件包中提供了以下規則:
+ `In(...interface{}):`檢查是否可以在給定的值列表中找到一個值。
+ `Length(min, max int)`:檢查值的長度是否在指定范圍內。此規則僅應用于驗證字符串,切片,映射和數組。
+ `RuneLength(min, max int):`檢查字符串的長度是否在指定范圍內。此規則與Length其他規則類似,不同之處在于,當要驗證的值是字符串時,它將檢查其符文長度而不是字節長度。
+ `Min(min interface{})和Max(max interface{}):`檢查值是否在指定范圍內。這兩個規則僅應用于驗證int,uint,float和time.Time類型。
+ `Match(*regexp.Regexp):`檢查值是否與指定的正則表達式匹配。此規則僅應用于字符串和字節片。
+ `Date(layout string):`檢查字符串值是否是布局指定格式的日期。通過調用Min()和/或Max(),您可以額外檢查日期是否在指定范圍內。
+ `Required:`檢查值是否不為空(nil也不為零)。
+ `NotNil:`檢查指針值是否不為nil。非指針值被視為有效。
+ `NilOrNotEmpty:`檢查值是nil指針還是非空值。這與Required將nil指針視為有效指針不同。
+ `Skip:`這是一條特殊規則,用于指示應跳過其后的所有規則(包括嵌套規則)。
+ `MultipleOf:`檢查該值是否為指定范圍的倍數。
+ `Each(rules ...Rule):`使用其他規則檢查可迭代(映射/切片/數組)中的元素。
該is子包提供了可用于檢查的常用字符串的驗證規則列表,如果值滿足一定要求的格式。請注意,這些規則僅處理字符串和字節片,并且如果字符串或字節片為空,則視為有效。您可以使用Required規則來確保值不為空。以下是該is軟件包提供的規則的完整列表:
+ `Email:`驗證字符串是否為電子郵件
+ `URL:`驗證字符串是否為有效網址
+ `RequestURL:`驗證字符串是否為有效的請求網址
+ `RequestURI:`驗證字符串是否為有效的請求URI
+ `Alpha:`驗證字符串是否僅包含英文字母(a-zA-Z)
+ `Digit:`驗證字符串是否僅包含數字(0-9)
+ `Alphanumeric:`驗證字符串是否僅包含英文字母和數字(a-zA-Z0-9)
+ `UTFLetter:`驗證字符串是否僅包含unicode字母
+ `UTFDigit:`驗證字符串是否僅包含unicode十進制數字
+ `UTFLetterNumeric:`驗證字符串是否僅包含unicode字母和數字
+ `UTFNumeric:`驗證字符串是否僅包含unicode數字字符(類別N)
+ `LowerCase:`驗證字符串是否僅包含小寫unicode字母
+ `UpperCase:`驗證字符串是否僅包含大寫unicode字母
+ `Hexadecimal:`驗證字符串是否為有效的十六進制數字
+ `HexColor:`驗證字符串是否為有效的十六進制顏色代碼
+ `RGBColor:`驗證字符串是否為rgb(R,G,B)形式的有效RGB顏色
+ `Int:`驗證字符串是否為有效的整數
+ `Float:`驗證字符串是否為浮點數
+ `UUIDv3:`驗證字符串是否為有效的版本3 UUID
+ `UUIDv4:`驗證字符串是否為有效的版本4 UUID
+ `UUIDv5:`驗證字符串是否為有效的版本5 UUID
+ `UUID:`驗證字符串是否為有效的UUID
+ `CreditCard:`驗證字符串是否為有效的信用卡號
+ `ISBN10:`驗證字符串是否為ISBN版本10
+ `ISBN13:`驗證字符串是否為ISBN版本13
+ `ISBN:`驗證字符串是否為ISBN(版本10或13)
+ `JSON:`驗證字符串是否為有效JSON格式
+ `ASCII:`驗證字符串是否僅包含ASCII字符
+ `PrintableASCII:`驗證字符串是否僅包含可打印的ASCII字符
+ `Multibyte:`驗證字符串是否包含多字節字符
+ `FullWidth:`驗證字符串是否包含全角字符
+ `HalfWidth:`驗證字符串是否包含半角字符
+ `VariableWidth:`驗證字符串是否同時包含全角和半角字符
+ `Base64:`驗證字符串是否在Base64中編碼
+ `DataURI:`驗證字符串是否為有效的base64編碼數據URI
+ `E164:`驗證字符串是否為有效的E164電話號碼(+19251232233)
+ `CountryCode2:`驗證字符串是否為有效的ISO3166 Alpha 2國家/地區代碼
+ `CountryCode3:`驗證字符串是否為有效的ISO3166 Alpha 3國家/地區代碼
+ `DialString:`驗證字符串是否是可以傳遞給Dial()的有效撥號字符串
+ `MAC:`驗證字符串是否為MAC地址
+ `IP:`驗證字符串是否為有效的IP地址(版本4或6)
+ `IPv4:`驗證字符串是否為有效的版本4 IP地址
+ `IPv6:`驗證字符串是否為有效的版本6 IP地址
+ `Subdomain:`驗證字符串是否為有效的子域
+ `Domain:`驗證字符串是否為有效域
+ `DNSName:`驗證字符串是否為有效的DNS名稱
+ `Host:`驗證字符串是有效的IP(v4和v6)還是有效的DNS名稱
+ `Port:`驗證字符串是否為有效的端口號
+ `MongoID:`驗證字符串是否為有效的Mongo ID
+ `Latitude:`驗證字符串是否為有效緯度
+ `Longitude:`驗證字符串是否為有效經度
+ `SSN:`驗證字符串是否為社會安全號碼(SSN)
+ `Semver:`驗證字符串是否是有效的語義版本
### 自定義錯誤消息
所有內置的驗證規則均允許您自定義錯誤消息。為此,只需調用Error()規則的方法。例如,
```go
data := "2123"
err := validation.Validate(data,
validation.Required.Error("is required"),
validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("must be a string with five digits"),
)
fmt.Println(err)
// Output:
// must be a string with five digits
```
### 創建自定義規則
創建自定義規則就像實現validation.Rule界面一樣簡單。接口包含如下所示的單個方法,該方法應驗證值并返回驗證錯誤(如果有):
```go
// Validate validates a value and returns an error if validation fails.
Validate(value interface{}) error
```
如果您已經具有與上面所示相同的簽名的函數,則可以調用validation.By()將其轉換為驗證規則。例如,
```go
func checkAbc(value interface{}) error {
s, _ := value.(string)
if s != "abc" {
return errors.New("must be abc")
}
return nil
}
err := validation.Validate("xyz", validation.By(checkAbc))
fmt.Println(err)
// Output: must be abc
```
如果您的驗證功能使用其他參數,則可以使用以下閉合技巧:
```go
func stringEquals(str string) validation.RuleFunc {
return func(value interface{}) error {
s, _ := value.(string)
if s != str {
return errors.New("unexpected string")
}
return nil
}
}
err := validation.Validate("xyz", validation.By(stringEquals("abc")))
fmt.Println(err)
// Output: unexpected string
```
### 規則組
在多個地方使用多個規則的組合時,可以使用以下技巧來創建規則組,以使代碼更易于維護。
```go
var NameRule = []validation.Rule{
validation.Required,
validation.Length(5, 20),
}
type User struct {
FirstName string
LastName string
}
func (u User) Validate() error {
return validation.ValidateStruct(&u,
validation.Field(&u.FirstName, NameRule...),
validation.Field(&u.LastName, NameRule...),
)
}
```
在上面的示例中,我們創建了一個NameRule包含兩個驗證規則的規則組。然后,我們使用此規則組同時驗證FirstName和LastName。
### 上下文感知驗證
雖然大多數驗證規則是獨立的,但某些規則可能會動態依賴于上下文。規則可以實現 validation.RuleWithContext接口以支持所謂的上下文感知驗證。
要使用上下文驗證任意值,請調用validation.ValidateWithContext()。該context.Conext參數將被傳遞下去,以實現這些規則validation.RuleWithContext。
要使用上下文驗證結構的字段,請調用validation.ValidateStructWithContext()。
您可以通過同時實現validation.Rule和來定義上下文感知規則validation.RuleWithContext。您還可以使用validation.WithContext()將功能轉換為上下文感知規則。例如,
```go
rule := validation.WithContext(func(ctx context.Context, value interface{}) error {
if ctx.Value("secret") == value.(string) {
return nil
}
return errors.New("value incorrect")
})
value := "xyz"
ctx := context.WithValue(context.Background(), "secret", "example")
err := validation.ValidateWithContext(ctx, value, rule)
fmt.Println(err)
// Output: value incorrect
```
執行上下文感知驗證時,如果未實現規則validation.RuleWithContext, validation.Rule則將改用它。