---
# System prepended metadata

title: ozzo-validation
tags: [Golang Package]

---

###### 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 %}