5

介绍一个golang struct 校验工具【qvalid】

 3 years ago
source link: https://blog.csdn.net/oqqYuan1234567890/article/details/90342731
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

这个库是去年写的,主要用于结构体的校验。
比如字符串长度、数值大小、oneof,以及字符串的一些常见属性(email/url/ip)等。
项目的地址:https://github.com/miaomiao3/qvalid
因为不想用一些现成但很臃肿的库如govalidator,就自己写了一个,写的时候希望语法尽可能精简,借用了mongodb的一些约束,比如lt/lte gt/gte概念,来约束字符串的长度或者数值的值大小。由于开发的时候是针对某些特定场合用的,所以完备性是有待提高的(主要的代码大概花了半天那样子,就是说可能有bug哈,如果有兴趣可以提issue和pr)。但是基本功能是没问题的。
下面内容摘自repo的readme,懒得翻成中文了,将就看一下吧。

qvalid

powerful tool to validate struct’s exported fields

Features

  • validate field value of numbers(int/uint/float…)
  • validate field length of string/array/slice/map
  • support in check
  • when a field is slice and its element is struct/struct_pointer, qvalid auto validate this struct related element
  • when a field is string, support attribute check. e.g. email/ip/email…
  • pretty field output msg, use json tag first as field name

Install

go get -u github.com/miaomiao3/qvalid

Syntax

  • As for bound limit, it means length of string/array/slice/map, and value of numbers(int/uint/float…)
  • If ‘in’ was set, do not set bound limit
  • , and = reserved
  • [ and ] reserved except of in constraint

constraint description

constraintdescriptioncommentltlittle than, upper bound limitu can set lt or lte!ltelittle than or equal, upper bound limitu can set lt or lte!gtgreater than, lower bound limitu can set gt or gte!gtegreater than or equal, lower bound limitu can set gt or gte!inmust in one of the list item. item character must be numeric or alphaIf ‘in’ was set, do not set bound limitattrwhen the field is string, it works to some known attribute like email, ip .etcattr desc

attr is available as below

const (
	StringTypeEmail        = "email"
	StringTypeAlpha        = "alpha"
	StringTypeUpperAlpha   = "upper_alpha"
	StringTypeLowerAlpha   = "lower_alpha"
	StringTypeAlphaNumeric = "alpha_numeric"
	StringTypeNumeric      = "numeric"
	StringTypeInt          = "int"
	StringTypeFloat        = "float"
	StringTypeHex          = "hex"
	StringTypeAscii        = "ascii"
	StringTypeVisibleAscii = "visible_ascii"
	StringTypeBytes        = "bytes"
	StringTypeBase64       = "base64"
	StringTypeDNS          = "dns"
	StringTypeVersion      = "version"
	StringTypeIp           = "ip"
	StringTypePort         = "port"
	StringTypeURL          = "url"
)

Examples

First, define some struct:


type Dog struct {
	Name      string            `valid:"in=[rose,tulip]" json:"name"`
	Color     string            `valid:"lt=5, gte=3" json:"color"`
	Weight    float64           `valid:"lt=100, gte=10" json:"weight"`
	Clothes   int               `valid:"in=[1,3,5]" json:"clothes"`
	NickNames []string          `valid:"lt=5, gt=1"`
	Relations map[string]string `valid:"lt=5, gt=1"`
	Email     string            `valid:"attr=email"`
	from      string            `json:"from" valid:"lt=10, gt=1"` // unexported, will be ignored by qvalid
}

type BadTag struct {
	Err1 string `valid:"lt=10, lte=1"`            // this will cause [qvalid] error msg
	Err2 string `valid:"gt=10, gte=1"`            // this will cause [qvalid] error msg
	Err3 string `valid:"lt=10, gt=1, in=[aa,bb]"` // this will cause [qvalid] error msg
	Err4 string `valid:"lt=1, gte=1"`             // this will cause [qvalid] error msg
}

type FakeFood struct {
	Leaf     Leaf
	MainLeaf *Leaf
}

type Food struct {
	Leafs []Leaf `valid:"gte=1"`
}

type Leaf struct {
	Name string `valid:"in=[rose,tulip]" json:"name"`
}


validate struct with simple field

like Dog


func validateSimpleField() {
	dog := &Dog{}
	isPass, validErrors := qvalid.ValidateStruct(dog)
	fmt.Println("validateSimpleField")
	checkAndDumpValidErrors(isPass, validErrors)

	newFlower := &Dog{
		Name:      "rose",
		Color:     "gray",
		Weight:    30.0,
		Clothes:   3,
		NickNames: []string{"wangcai", "dawang"},
		Relations: map[string]string{
			"owner": "cy",
			"birth": "2018",
		},
		Email: "[email protected]",
	}
	isPass, validErrors = qvalid.ValidateStruct(newFlower)
	checkAndDumpValidErrors(isPass, validErrors)
}

output:

validateSimpleField
    illegal input and result:
        isPass:false
        validErrors:
            err:0 --> &{Field:.name Msg:value: not in:[rose tulip]}
            err:1 --> &{Field:.color Msg:expect length >= 3 but get length: 0}
            err:2 --> &{Field:.weight Msg:expect value >= 10 but get value:0}
            err:3 --> &{Field:.clothes Msg:value:0 not in:[1 3 5]}
            err:4 --> &{Field:.NickNames Msg:expect length > 1 but get length: 0}
            err:5 --> &{Field:.Relations Msg:expect length > 1 but get length: 0}
            err:6 --> &{Field:.Email Msg:value: not match attribute:email}

    legal input and result:
        isPass:true

validate embedded struct

like FakeFood


func validateEmbedStruct() {
	food := &FakeFood{
		MainLeaf: &Leaf{},
	}
	isPass, validErrors := qvalid.ValidateStruct(food)
	fmt.Println("validateEmbedStruct")
	checkAndDumpValidErrors(isPass, validErrors)

	newFakeFood := FakeFood{
		Leaf: Leaf{
			Name: "rose",
		},
		MainLeaf: &Leaf{
			Name: "rose",
		},
	}
	isPass, validErrors = qvalid.ValidateStruct(newFakeFood)
	checkAndDumpValidErrors(isPass, validErrors)
}

output


validateEmbedStruct
    illegal input and result:
        isPass:false
        validErrors:
            err:0 --> &{Field:.Leaf.name Msg:value: not in:[rose tulip]}
            err:1 --> &{Field:.MainLeaf.name Msg:value: not in:[rose tulip]}

    legal input and result:
        isPass:true
        

validate slice embedded struct

like Food


func validateSliceEmbedStruct() {
	food := &Food{
		Leafs: []Leaf{ // if Leafs is empty, qvalid do not check empty slice field, so set 1 element to test
			Leaf{},
		},
	}
	isPass, validErrors := qvalid.ValidateStruct(food)
	fmt.Println("validateSliceEmbedStruct")
	checkAndDumpValidErrors(isPass, validErrors)

	newFood := Food{
		Leafs: []Leaf{
			Leaf{
				Name: "rose",
			},
		},
	}
	isPass, validErrors = qvalid.ValidateStruct(newFood)
	checkAndDumpValidErrors(isPass, validErrors)
}

output


validateSliceEmbedStruct
    illegal input and result:
        isPass:false
        validErrors:
            err:0 --> &{Field:.Leafs[0].name Msg:value: not in:[rose tulip]}

    legal input and result:
        isPass:true
        

sample of bad tag

like Person


func badTag() {
	bad := &BadTag{}

	isPass, validErrors := qvalid.ValidateStruct(bad)
	fmt.Println("badTag")
	checkAndDumpValidErrors(isPass, validErrors)
}

output


badTag
    illegal input and result:
        isPass:false
        validErrors:
            err:0 --> &{Field:[qvalid] GetConstraintFromTag Msg:lt and lte can't both set}
            err:1 --> &{Field:[qvalid] GetConstraintFromTag Msg:gt and gt can't both set}
            err:2 --> &{Field:[qvalid] GetConstraintFromTag Msg:bound limit and 'in' can't both set}
            err:3 --> &{Field:[qvalid] GetConstraintFromTag Msg:upper and lower bound limit illegal}
            

for more details, see example dir.

TODO:

  1. check pointer loop
  2. customized field validator

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK