Golang验证器多字段依赖

huangapple go评论85阅读模式
英文:

Golang validator multifield dependency

问题

我想验证以下结构:

type CarModel struct {
    gorm.Model
    OwnerID int    `json:"ownerid" validate:"nonzero"`
    Type    string `json:"type" validate:"regexp=(?)(A|B)"`
    A       string `json:"url" validate:"isurl"`
    B       string `json:"ip" validate:"isip"`
}

我想根据Type验证A和B:

  • 如果Type为A,则A必须存在且必须是一个URL,但B不能存在。
  • 如果Type为B,则A不能存在且B必须是一个IP。

使用验证器(validator)可以实现这个吗?

我尝试了自定义验证,但无法找到一种方法来获取Type的值:

func checkB(v interface{}, param string) error {
    theB := reflect.ValueOf(v)
    if theB.Kind() != reflect.String {
        return validator.ErrUnsupported
    }
    // 检查B是否为IP
    ipcool := net.ParseIP(theB.String())
    if ipcool == nil {
        return errors.New("B : ip incorrecte " + theB.String())
    }
    return nil
}

根据Alex Nichol的回答,首先感谢您的帮助。

如果我理解正确,我需要遍历所有的"validate"字段,以保留TYPE、A和B的值,并根据TYPE进行检查...

我做了这个:

func checkMonitor(v interface{}) error {
    var mytype string
    var myA string
    var myB string

    val := reflect.ValueOf(v)
    // 遍历字段
    for i := 0; i < val.NumField(); i++ {
        // 查找validate标签
        field := val.Type().Field(i)
        tags := field.Tag
        _, ok := tags.Lookup("validate")
        if !ok {
            // 没有validate标签。
            continue
        }

        // 获取字段的值。
        fieldValue := val.Field(i)

        switch field.Name {
        case "Type":
            mytype = fieldValue.String()
        case "A":
            myA = fieldValue.String()
        case "B":
            myB = fieldValue.String()
        }
        // 在这里进行验证逻辑。
        //fmt.Println("field", field.Name, "has validate tag", validate, "and value", fieldValue.Interface())
    }
    if mytype == "A" {
        if myA == "" {
            return errors.New("A vide et type A")
        }
        ipcool := net.ParseIP(myA)
        if ipcool == nil {
            return errors.New("A incorrecte " + myA)
        }
    } else if mytype == "HTML" {
        if myB == "" {
            return errors.New("B vide et type B")
        }
        _, urlpascool := url.ParseRequestURI(myB)
        if urlpascool != nil {
            return errors.New("B incorrecte " + myB)
        }
    }
    return nil
}

但在switch case中的mytype、myA和myB处出现错误:

cannot use fieldValue.Interface() (type interface {}) as type string in assignment: need type assertion

编辑:
只需要动动脑筋:

switch field.Name {
case "Type":
    mytype = fieldValue.String()
case "A":
    myA = fieldValue.String()
case "B":
    myB = fieldValue.String()
}
英文:

I'd like to validate the following structure :

> type CarModel struct {
> gorm.Model
> OwnerID int json:&quot;ownerid&quot; validate:&quot;nonzero&quot;
> Type string json:&quot;type&quot; validate:&quot;regexp=(?)(A|B)&quot;
> A string json:&quot;url&quot; validate:&quot;isurl&quot;
> B string json:&quot;ip&quot; validate:&quot;isip&quot;
> }

I would like to validate A and B depending on Type,
if type = A then A must exist and must be a URL BUT B must not exist
if type = B then A must not exist and B must be an IP

is this possible with validator ?

I did try custom validation but I cannot find a way to see type value :

func checkB(v interface{}, param string) error {
theB := reflect.ValueOf(v)
if theB.Kind() != reflect.String {
return validator.ErrUnsupported
}
//check if B is an IP
ipcool := net.ParseIP(theB.String())
if ipcool == nil {
return errors.New(&quot;B : ip incorrecte &quot; + theB.String())
}
return nil
}

Upon the answer of Alex Nichol, I would like first to thank you for your help.

If I understood correctly, I would have to iterate through all the "validate" fields, to keep a trace of the value of TYPE, A and B and then to check them depending on TYPE ...

I did this :

func checkMonitor(v interface{}) error {
var mytype string
var myA string
var myB string
val := reflect.ValueOf(v)
// Iterate through fields
for i := 0; i &lt; val.NumField(); i++ {
// Lookup the validate tag
field := val.Type().Field(i)
tags := field.Tag
_, ok := tags.Lookup(&quot;validate&quot;)
if !ok {
// No validate tag.
continue
}
// Get the value of the field.
fieldValue := val.Field(i)
switch field.Name {
case &quot;Type&quot;:
mytype = fieldValue.Interface()
case &quot;A&quot;:
myA = fieldValue.Interface()
case &quot;B&quot;:
myB = fieldValue.Interface()
}
// Validation logic here.
//fmt.Println(&quot;field&quot;, field.Name, &quot;has validate tag&quot;, validate, &quot;and value&quot;, fieldValue.Interface())
}
if mytype == &quot;A&quot; {
if myA == &quot;&quot; {
return errors.New(&quot;A vide et type A&quot;)
}
ipcool := net.ParseIP(myA)
if ipcool == nil {
return errors.New(&quot;A incorrecte &quot; + myA)
}
} else if mytype == &quot;HTML&quot; {
if myB == &quot;&quot; {
return errors.New(&quot;B vide et type B&quot;)
}
_, urlpascool := url.ParseRequestURI(myB)
if urlpascool != nil {
return errors.New(&quot;B incorrecte &quot; + myB)
}
}
return nil
}

but got an error on the mytype, myA and myB in the switch case :

cannot use fieldValue.Interface() (type interface {}) as type string in assignment: need type assertion

EDIT :
just needed to use my brain :

switch field.Name {
case &quot;Type&quot;:
mytype = fieldValue.String()
case &quot;A&quot;:
myA = fieldValue.String()
case &quot;B&quot;:
myB = fieldValue.Interface()
}

答案1

得分: 1

你可能想要使用反射来迭代结构体的字段,获取每个字段的validate标签,并对字段进行检查。这意味着你需要在结构体级别上进行验证。否则,如果你将类似myInstance.OwnerID的内容传递给函数,你将丢失与之关联的标签。

以下代码循环遍历结构体的字段,并获取每个字段的validate标签:

func checkStruct(v interface{}) error {
    val := reflect.ValueOf(v)

    // 遍历字段
    for i := 0; i < val.NumField(); i++ {
        // 查找 validate 标签
        field := val.Type().Field(i)
        tags := field.Tag
        validate, ok := tags.Lookup("validate")
        if !ok {
            // 没有 validate 标签
            continue
        }

        // 获取字段的值
        fieldValue := val.Field(i)

        // 在这里进行验证逻辑
        fmt.Println("字段", field.Name, "具有 validate 标签", validate, "和值",
            fieldValue.Interface())
    }
    return nil
}

例如,我们可以将以下CarModel传递给它:

checkStruct(CarModel{
    OwnerID: 2,
    Type:    "B",
    A:       "http://google.com",
    B:       "192.168.1.1",
})

它将打印出以下内容:

字段 OwnerID 具有 validate 标签 nonzero 和值 2
字段 Type 具有 validate 标签 regexp=(?)(A|B) 和值 B
字段 A 具有 validate 标签 isurl 和值 http://google.com
字段 B 具有 validate 标签 isip 和值 192.168.1.1
英文:

You probably want to use reflection to iterate over the fields of the struct, get the validate tag for each field, and check the field. This means you'll have to do validation on a struct level. Otherwise, if you pass something like myInstance.OwnerID to a function, you'll lose the tag associated with it.

This code loops through the fields of a struct and gets the validate tag for each:

func checkStruct(v interface{}) error {
val := reflect.ValueOf(v)
// Iterate through fields
for i := 0; i &lt; val.NumField(); i++ {
// Lookup the validate tag
field := val.Type().Field(i)
tags := field.Tag
validate, ok := tags.Lookup(&quot;validate&quot;)
if !ok {
// No validate tag.
continue
}
// Get the value of the field.
fieldValue := val.Field(i)
// Validation logic here.
fmt.Println(&quot;field&quot;, field.Name, &quot;has validate tag&quot;, validate, &quot;and value&quot;,
fieldValue.Interface())
}
return nil
}

For example, we could pass it the following CarModel:

checkStruct(CarModel{
OwnerID: 2,
Type:    &quot;B&quot;,
A:       &quot;http://google.com&quot;,
B:       &quot;192.168.1.1&quot;,
})

and it would print out the following:

field OwnerID has validate tag nonzero and value 2
field Type has validate tag regexp=(?)(A|B) and value B
field A has validate tag isurl and value http://google.com
field B has validate tag isip and value 192.168.1.1

答案2

得分: 0

似乎你的验证规则非常复杂,但你可以尝试使用validating

假设我们已经有了两个自定义的验证器IsURLIsIP,那么你的验证规则可以按照以下方式实现:

import (
    "regexp"

    v "github.com/RussellLuo/validating"
)

// 自定义验证器
var (
    MatchRegexp = func(pattern string) v.Validator {
        return v.FromFunc(func(field v.Field) v.Errors {
            switch t := field.ValuePtr.(type) {
            case *string:
                if matched, _ := regexp.MatchString(pattern, *t); !matched {
                    return v.NewErrors(field.Name, v.ErrInvalid, "不匹配")
                }
                return nil
            default:
                return v.NewErrors(field.Name, v.ErrUnsupported, "不支持")
            }
        })
    }
    // IsURL 和 IsIP 省略
)

type CarModel struct {
    gorm.Model
    OwnerID int    `json:"ownerid"`
    Type    string `json:"type"`
    A       string `json:"url"`
    B       string `json:"ip"`
}

func main() {
    car := CarModel{}
    errs := v.Validate(v.Schema{
        v.F("ownerid", &car.OwnerID): v.Nonzero(),
        v.F("type", &car.Type):       MatchRegexp("(A|B)"),
        v.F("a", &car.A): v.Lazy(func() v.Validator {
            if car.Type == "A" {
                return v.All(v.Nonzero(), IsURL())
            }
            return v.Not(v.Nonzero())
        }),
        v.F("b", &car.B): v.Lazy(func() v.Validator {
            if car.Type == "B" {
                return v.All(v.Nonzero(), IsIP())
            }
            return v.Not(v.Nonzero())
        }),
    })
}
英文:

Seems like your validation rules are very complicated, but you can give validating a try.

Suppose we already have two customized validators IsURL and IsIP, then your validation rules can be implemented as follows:

<!-- language: go -->

import (
&quot;regexp&quot;
v &quot;github.com/RussellLuo/validating&quot;
)
// Customized validators
var (
MatchRegexp = func(pattern string) v.Validator {
return v.FromFunc(func(field v.Field) v.Errors {
switch t := field.ValuePtr.(type) {
case *string:
if matched, _ := regexp.MatchString(pattern, *t); !matched {
return v.NewErrors(field.Name, v.ErrInvalid, &quot;does not match&quot;)
}
return nil
default:
return v.NewErrors(field.Name, v.ErrUnsupported, &quot;is unsupported&quot;)
}
})
}
// IsURL and IsIP are omitted
)
type CarModel struct {
gorm.Model
OwnerID    int    `json:&quot;ownerid&quot;`
Type       string `json:&quot;type&quot;`
A          string `json:&quot;url&quot;`
B          string `json:&quot;ip&quot;`
}
func main() {
car := CarModel{}
errs := v.Validate(v.Schema{
v.F(&quot;ownerid&quot;, &amp;car.OwnerID): v.Nonzero(),
v.F(&quot;type&quot;, &amp;car.Type):       MatchRegexp(&quot;(A|B)&quot;),
v.F(&quot;a&quot;, &amp;car.A): v.Lazy(func() v.Validator {
if car.Type == &quot;A&quot; {
return v.All(v.Nonzero(), IsURL())
}
return v.Not(v.Nonzero())
}),
v.F(&quot;b&quot;, &amp;car.B): v.Lazy(func() v.Validator {
if car.Type == &quot;B&quot; {
return v.All(v.Nonzero(), IsIP())
}
return v.Not(v.Nonzero())
}),
})
}

huangapple
  • 本文由 发表于 2017年2月22日 19:50:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/42390574.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定