feat(Go-Tool):2020/12/14:添加validator.v8源码解析和使用示例
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package heap provides heap operations for any type that implements
|
||||
// heap.Interface. A heap is a tree with the property that each node is the
|
||||
// minimum-valued node in its subtree.
|
||||
//
|
||||
// The minimum element in the tree is the root, at index 0.
|
||||
//
|
||||
// A heap is a common way to implement a priority queue. To build a priority
|
||||
// queue, implement the Heap interface with the (negative) priority as the
|
||||
// ordering for the Less method, so Push adds items while Pop removes the
|
||||
// highest-priority item from the queue. The Examples include such an
|
||||
// implementation; the file example_pq_test.go has the complete source.
|
||||
//
|
||||
//read note 拷贝一个代码过来解析.感觉在go里面拷贝代码简单多了,依赖可以直接使用。我在这边使用read note(自定义todo标识)来标识我的解析.
|
||||
package sourceAnalysis
|
||||
|
||||
import "sort"
|
||||
|
||||
// The Interface type describes the requirements
|
||||
// for a type using the routines in this package.
|
||||
// Any type that implements it may be used as a
|
||||
// min-heap with the following invariants (established after
|
||||
// Init has been called or if the data is empty or sorted):
|
||||
//
|
||||
// !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
|
||||
//
|
||||
// Note that Push and Pop in this interface are for package heap's
|
||||
// implementation to call. To add and remove things from the heap,
|
||||
// use heap.Push and heap.Pop.
|
||||
type Interface interface {
|
||||
sort.Interface
|
||||
Push(x interface{}) // add x as element Len()
|
||||
Pop() interface{} // remove and return element Len() - 1.
|
||||
}
|
||||
|
||||
// Init establishes the heap invariants required by the other routines in this package.
|
||||
// Init is idempotent with respect to the heap invariants
|
||||
// and may be called whenever the heap invariants may have been invalidated.
|
||||
// The complexity is O(n) where n = h.Len().
|
||||
//read note 对整个Interface 进行重构,时间复杂度是 O(n)
|
||||
func Init(h Interface) {
|
||||
// heapify
|
||||
n := h.Len()
|
||||
//read note 从最小父节点,到第0位的根节点,分别向下进行重构处理(之所以要用循环是因为一次向下的重构,只能对一条连续的分支进行重构,不彻底.)
|
||||
for i := n/2 - 1; i >= 0; i-- {
|
||||
down(h, i, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Push pushes the element x onto the heap.
|
||||
// The complexity is O(log n) where n = h.Len().
|
||||
// read note 这个Push方法是往 Interface里面去新增一个元素.和我们继承的Push方法有差别.这边同时候做了重构操作.
|
||||
// 一次Push的时间复杂度是 O(logN),一次Init的时间复杂度是O(N),按道理Push的元素越多,Init的效率越高
|
||||
func Push(h Interface, x interface{}) {
|
||||
//read note 先把元素添加进去
|
||||
h.Push(x)
|
||||
//read note 然后在从下往上进行重构处理,正常情况下Push都是把元素添加在最后一个位置,如果实现的方法,把Push添加到其他位置如果不进行Init,应该是会有问题.
|
||||
up(h, h.Len()-1)
|
||||
}
|
||||
|
||||
// Pop removes and returns the minimum element (according to Less) from the heap.
|
||||
// The complexity is O(log n) where n = h.Len().
|
||||
// Pop is equivalent to Remove(h, 0).
|
||||
//read note 把最小的元素输出,需要注意的是真正的Pop必须是调用这个方法,而不是我们继承的那个方法.
|
||||
// 时间复杂度是O(logN)
|
||||
func Pop(h Interface) interface{} {
|
||||
//read note 真正的Pop输出的是最小的元素,也就是最小堆的第0位置的元素
|
||||
// 所以这边的操作是把第0位的数组放到最后一位,然后从位置0开始,到N-1的位置,对所有元素进行down(父子节点比较交换)的操作
|
||||
n := h.Len() - 1
|
||||
h.Swap(0, n)
|
||||
down(h, 0, n)
|
||||
return h.Pop()
|
||||
}
|
||||
|
||||
// Remove removes and returns the element at index i from the heap.
|
||||
// The complexity is O(log n) where n = h.Len().
|
||||
//read note 移除某个位置的元素,时间复杂度是 o(logn)
|
||||
func Remove(h Interface, i int) interface{} {
|
||||
//read note 判断移除的下标不等于最后一个元素位置,要特殊处理
|
||||
n := h.Len() - 1
|
||||
if n != i {
|
||||
//read note 交换第i个元素和最后一个元素
|
||||
h.Swap(i, n)
|
||||
//read note Fix的处理操作,这边只会处理到移除一个元素后的位置,也就是说被移除的那个元素(在最后的位置)不会参与重构数组的操作
|
||||
if !down(h, i, n) {
|
||||
up(h, i)
|
||||
}
|
||||
}
|
||||
//read note 调用Pop,把最后一个元素返回回去
|
||||
return h.Pop()
|
||||
}
|
||||
|
||||
// Fix re-establishes the heap ordering after the element at index i has changed its value.
|
||||
// Changing the value of the element at index i and then calling Fix is equivalent to,
|
||||
// but less expensive than, calling Remove(h, i) followed by a Push of the new value.
|
||||
// The complexity is O(log n) where n = h.Len().
|
||||
//read note 当下标为i的元素发生改变,需要进行一次Fix的处理,时间复杂度是 o(logn)
|
||||
func Fix(h Interface, i int) {
|
||||
//read note 想从下标i的这个元素往下查找处理,如果往下没有交换元素,再往上进行处理.
|
||||
if !down(h, i, h.Len()) {
|
||||
up(h, i)
|
||||
}
|
||||
}
|
||||
|
||||
//read note h: 对应的数组数据
|
||||
//read note j:子节点的下标
|
||||
//read note 方法作用
|
||||
func up(h Interface, j int) {
|
||||
for {
|
||||
//read note 拿到对应的父节点的下标
|
||||
i := (j - 1) / 2 // parent
|
||||
//read note 找到最后一个父节点 || 父节点比子节点小,则跳出循环
|
||||
if i == j || !h.Less(j, i) {
|
||||
break
|
||||
}
|
||||
//read note 否则就是父节点比子节点的值大,需要交换对应的元素,然后找到父节点的下标,再往上找其父节点的关系
|
||||
h.Swap(i, j)
|
||||
j = i
|
||||
}
|
||||
}
|
||||
|
||||
//read note h: 对应的数组数据
|
||||
//read note i0: 需要下发处理的坐标,这边也就是左右子节点的父节点下标.
|
||||
//read note n: 数组对应的总长度
|
||||
//read note 方法作用:从父节点开始,循环向下判断对应的元素是否在对应的环境上
|
||||
func down(h Interface, i0, n int) bool {
|
||||
//read note 把父节点的下标拿出来
|
||||
i := i0
|
||||
|
||||
//read note 循环的结束条件是:
|
||||
for {
|
||||
//read note 找到左边子节点下标
|
||||
j1 := 2*i + 1
|
||||
//read note 退出条件1:超过最大长度或者是负值(这个应该是针对传入就有问题的处理)
|
||||
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
|
||||
break
|
||||
}
|
||||
//read note 拿到左孩子和右孩子中比较小的那个元素(的下标)
|
||||
j := j1 // left child
|
||||
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
|
||||
j = j2 // = 2*i + 2 // right child
|
||||
}
|
||||
|
||||
//read note 判断父节点和子节点的大小关系,小的元素应该在父节点,所以如果子节点本身就比较小,直接退出循环,否则交换元素
|
||||
//read note 然后再把下标移动到被交换的这个元素上,计算被交换的这个元素和它的左右子节点的大小关系,进入下一个循环
|
||||
if !h.Less(j, i) {
|
||||
break
|
||||
}
|
||||
h.Swap(i, j)
|
||||
i = j
|
||||
}
|
||||
//read note 判断是否发生了交换操作
|
||||
return i > i0
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package list implements a doubly linked list.
|
||||
//
|
||||
// To iterate over a list (where l is a *List):
|
||||
// for e := l.Front(); e != nil; e = e.Next() {
|
||||
// // do something with e.Value
|
||||
// }
|
||||
//
|
||||
package sourceAnalysis
|
||||
|
||||
// Element is an element of a linked list.
|
||||
//read note list中对应的元素,list相当是通过一个链表来链接所有的Element元素.
|
||||
type Element struct {
|
||||
// Next and previous pointers in the doubly-linked list of elements.
|
||||
// To simplify the implementation, internally a list l is implemented
|
||||
// as a ring, such that &l.root is both the next element of the last
|
||||
// list element (l.Back()) and the previous element of the first list
|
||||
// element (l.Front()).
|
||||
//read note 指向下一个和前一个元素的指针.internally a list l is implemented as a ring:内部实现实际是一个环
|
||||
next, prev *Element
|
||||
|
||||
// The list to which this element belongs.
|
||||
//read note 链表头,这边是设置表头为哨兵的模式进行链表的处理的
|
||||
list *List
|
||||
|
||||
// The value stored with this element.
|
||||
//read note Element中实际的元素值
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Next returns the next list element or nil.
|
||||
func (e *Element) Next() *Element {
|
||||
//read note 获取下一个元素,这边判断条件包含 list不为空,以及next不为链表头结点(哨兵).
|
||||
if p := e.next; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prev returns the previous list element or nil.
|
||||
func (e *Element) Prev() *Element {
|
||||
//read note 与Next一样的道理,判断list不为空,以及pre不等于链表头结点
|
||||
if p := e.prev; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List represents a doubly linked list.
|
||||
// The zero value for List is an empty list ready to use.
|
||||
//read note 设置哨兵的链表实现
|
||||
type List struct {
|
||||
//read note 哨兵结点
|
||||
root Element // sentinel list element, only &root, root.prev, and root.next are used
|
||||
//read note 链表长度
|
||||
len int // current list length excluding (this) sentinel element
|
||||
}
|
||||
|
||||
// Init initializes or clears list l.
|
||||
func (l *List) Init() *List {
|
||||
//read note 初始化的时候,链表的哨兵结点pre和next是互相指向的,这也可以作为判断链表是否为空的依据
|
||||
l.root.next = &l.root
|
||||
l.root.prev = &l.root
|
||||
l.len = 0
|
||||
return l
|
||||
}
|
||||
|
||||
// New returns an initialized list.
|
||||
func New() *List { return new(List).Init() }
|
||||
|
||||
// Len returns the number of elements of list l.
|
||||
// The complexity is O(1).
|
||||
func (l *List) Len() int { return l.len }
|
||||
|
||||
// Front returns the first element of list l or nil if the list is empty.
|
||||
// read note 返回第一个元素,如果链表不为空,则返回哨兵结点的next就是一个链表结点
|
||||
func (l *List) Front() *Element {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.next
|
||||
}
|
||||
|
||||
// Back returns the last element of list l or nil if the list is empty.
|
||||
// read note 所以list是一个环,如果链表不为空,则最后一个元素可以通过哨兵的prev来获取到.
|
||||
func (l *List) Back() *Element {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.prev
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes a zero List value.
|
||||
func (l *List) lazyInit() {
|
||||
if l.root.next == nil {
|
||||
l.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// insert inserts e after at, increments l.len, and returns e.
|
||||
//read note 在某一个位置后面加入一个element,只需要设置一下长度,前后链表地址的指向即可。
|
||||
func (l *List) insert(e, at *Element) *Element {
|
||||
n := at.next
|
||||
at.next = e
|
||||
e.prev = at
|
||||
e.next = n
|
||||
n.prev = e
|
||||
e.list = l
|
||||
l.len++
|
||||
return e
|
||||
}
|
||||
|
||||
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
|
||||
func (l *List) insertValue(v interface{}, at *Element) *Element {
|
||||
return l.insert(&Element{Value: v}, at)
|
||||
}
|
||||
|
||||
// remove removes e from its list, decrements l.len, and returns e.
|
||||
//read note 移除某个元素,返回该元素并设置不相关的属性为空,防止内存泄露.
|
||||
func (l *List) remove(e *Element) *Element {
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
e.next = nil // avoid memory leaks
|
||||
e.prev = nil // avoid memory leaks
|
||||
e.list = nil
|
||||
l.len--
|
||||
return e
|
||||
}
|
||||
|
||||
// move moves e to next to at and returns e.
|
||||
//read note 移动某个元素,本质上和插入某个元素有点类似.即移除对应节点后,再在对应的节点后面插入该元素
|
||||
func (l *List) move(e, at *Element) *Element {
|
||||
if e == at {
|
||||
return e
|
||||
}
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
|
||||
n := at.next
|
||||
at.next = e
|
||||
e.prev = at
|
||||
e.next = n
|
||||
n.prev = e
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Remove removes e from l if e is an element of list l.
|
||||
// It returns the element value e.Value.
|
||||
// The element must not be nil.
|
||||
func (l *List) Remove(e *Element) interface{} {
|
||||
if e.list == l {
|
||||
// if e.list == l, l must have been initialized when e was inserted
|
||||
// in l or l == nil (e is a zero Element) and l.remove will crash
|
||||
l.remove(e)
|
||||
}
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// PushFront inserts a new element e with value v at the front of list l and returns e.
|
||||
func (l *List) PushFront(v interface{}) *Element {
|
||||
l.lazyInit()
|
||||
return l.insertValue(v, &l.root)
|
||||
}
|
||||
|
||||
// PushBack inserts a new element e with value v at the back of list l and returns e.
|
||||
func (l *List) PushBack(v interface{}) *Element {
|
||||
l.lazyInit()
|
||||
return l.insertValue(v, l.root.prev)
|
||||
}
|
||||
|
||||
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
|
||||
// If mark is not an element of l, the list is not modified.
|
||||
// The mark must not be nil.
|
||||
func (l *List) InsertBefore(v interface{}, mark *Element) *Element {
|
||||
if mark.list != l {
|
||||
return nil
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
return l.insertValue(v, mark.prev)
|
||||
}
|
||||
|
||||
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
|
||||
// If mark is not an element of l, the list is not modified.
|
||||
// The mark must not be nil.
|
||||
func (l *List) InsertAfter(v interface{}, mark *Element) *Element {
|
||||
if mark.list != l {
|
||||
return nil
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
return l.insertValue(v, mark)
|
||||
}
|
||||
|
||||
// MoveToFront moves element e to the front of list l.
|
||||
// If e is not an element of l, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *List) MoveToFront(e *Element) {
|
||||
if e.list != l || l.root.next == e {
|
||||
return
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
l.move(e, &l.root)
|
||||
}
|
||||
|
||||
// MoveToBack moves element e to the back of list l.
|
||||
// If e is not an element of l, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *List) MoveToBack(e *Element) {
|
||||
if e.list != l || l.root.prev == e {
|
||||
return
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
l.move(e, l.root.prev)
|
||||
}
|
||||
|
||||
// MoveBefore moves element e to its new position before mark.
|
||||
// If e or mark is not an element of l, or e == mark, the list is not modified.
|
||||
// The element and mark must not be nil.
|
||||
func (l *List) MoveBefore(e, mark *Element) {
|
||||
if e.list != l || e == mark || mark.list != l {
|
||||
return
|
||||
}
|
||||
l.move(e, mark.prev)
|
||||
}
|
||||
|
||||
// MoveAfter moves element e to its new position after mark.
|
||||
// If e or mark is not an element of l, or e == mark, the list is not modified.
|
||||
// The element and mark must not be nil.
|
||||
func (l *List) MoveAfter(e, mark *Element) {
|
||||
if e.list != l || e == mark || mark.list != l {
|
||||
return
|
||||
}
|
||||
l.move(e, mark)
|
||||
}
|
||||
|
||||
// PushBackList inserts a copy of an other list at the back of list l.
|
||||
// The lists l and other may be the same. They must not be nil.
|
||||
func (l *List) PushBackList(other *List) {
|
||||
l.lazyInit()
|
||||
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
|
||||
l.insertValue(e.Value, l.root.prev)
|
||||
}
|
||||
}
|
||||
|
||||
// PushFrontList inserts a copy of an other list at the front of list l.
|
||||
// The lists l and other may be the same. They must not be nil.
|
||||
func (l *List) PushFrontList(other *List) {
|
||||
l.lazyInit()
|
||||
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
|
||||
l.insertValue(e.Value, &l.root)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ring implements operations on circular lists.
|
||||
package sourceAnalysis
|
||||
|
||||
// A Ring is an element of a circular list, or ring.
|
||||
// Rings do not have a beginning or end; a pointer to any ring element
|
||||
// serves as reference to the entire ring. Empty rings are represented
|
||||
// as nil Ring pointers. The zero value for a Ring is a one-element
|
||||
// ring with a nil Value.
|
||||
//
|
||||
//read note 环结构:指针可能指向任意元素,空链表只有一个nil元素
|
||||
type Ring struct {
|
||||
next, prev *Ring
|
||||
Value interface{} // for use by client; untouched by this library
|
||||
}
|
||||
|
||||
//read note 初始化只是做了指针指向,不会增加环的值
|
||||
func (r *Ring) init() *Ring {
|
||||
r.next = r
|
||||
r.prev = r
|
||||
return r
|
||||
}
|
||||
|
||||
// Next returns the next ring element. r must not be empty.
|
||||
//read note:环的下一个值,如果环的next为空,则初始化该环
|
||||
func (r *Ring) Next() *Ring {
|
||||
if r.next == nil {
|
||||
return r.init()
|
||||
}
|
||||
return r.next
|
||||
}
|
||||
|
||||
// Prev returns the previous ring element. r must not be empty.
|
||||
func (r *Ring) Prev() *Ring {
|
||||
if r.next == nil {
|
||||
return r.init()
|
||||
}
|
||||
return r.prev
|
||||
}
|
||||
|
||||
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
|
||||
// in the ring and returns that ring element. r must not be empty.
|
||||
//
|
||||
//read note 对环上的当前元素进行n次位置的移动,小于0则向后移动,大于0则向前移动
|
||||
func (r *Ring) Move(n int) *Ring {
|
||||
if r.next == nil {
|
||||
return r.init()
|
||||
}
|
||||
switch {
|
||||
case n < 0:
|
||||
for ; n < 0; n++ {
|
||||
r = r.prev
|
||||
}
|
||||
case n > 0:
|
||||
for ; n > 0; n-- {
|
||||
r = r.next
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// New creates a ring of n elements.
|
||||
//read note 创建n个节点的环
|
||||
func New1(n int) *Ring {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
r := new(Ring)
|
||||
p := r
|
||||
for i := 1; i < n; i++ {
|
||||
p.next = &Ring{prev: p}
|
||||
p = p.next
|
||||
}
|
||||
p.next = r
|
||||
r.prev = p
|
||||
return r
|
||||
}
|
||||
|
||||
// Link connects ring r with ring s such that r.Next()
|
||||
// becomes s and returns the original value for r.Next().
|
||||
// r must not be empty.
|
||||
//
|
||||
// If r and s point to the same ring, linking
|
||||
// them removes the elements between r and s from the ring.
|
||||
// The removed elements form a subring and the result is a
|
||||
// reference to that subring (if no elements were removed,
|
||||
// the result is still the original value for r.Next(),
|
||||
// and not nil).
|
||||
//
|
||||
// If r and s point to different rings, linking
|
||||
// them creates a single ring with the elements of s inserted
|
||||
// after r. The result points to the element following the
|
||||
// last element of s after insertion.
|
||||
//
|
||||
//read note 链接环,如果是两个相同的环则环不改变,链接完环会少掉原来的第一个元素....(神奇)
|
||||
// 如果是两个不同的环,则把另一个环接到上一个环的后面,链接完的第一个元素是原来环的next的元素.第二个元素
|
||||
func (r *Ring) Link(s *Ring) *Ring {
|
||||
n := r.Next()
|
||||
if s != nil {
|
||||
p := s.Prev()
|
||||
// Note: Cannot use multiple assignment because
|
||||
// evaluation order of LHS is not specified.
|
||||
r.next = s
|
||||
s.prev = r
|
||||
n.prev = p
|
||||
p.next = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Unlink removes n % r.Len() elements from the ring r, starting
|
||||
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
|
||||
// The result is the removed subring. r must not be empty.
|
||||
//
|
||||
//read note 断开环上n个元素,这里会根据n%r.Len 得到的余数进行Unlink,因为环是没有起点终点的,所以超过或者不超过环的长度都是按照余数进行处理
|
||||
func (r *Ring) Unlink(n int) *Ring {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
return r.Link(r.Move(n + 1))
|
||||
}
|
||||
|
||||
// Len computes the number of elements in ring r.
|
||||
// It executes in time proportional to the number of elements.
|
||||
//
|
||||
func (r *Ring) Len() int {
|
||||
n := 0
|
||||
if r != nil {
|
||||
n = 1
|
||||
for p := r.Next(); p != r; p = p.next {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Do calls function f on each element of the ring, in forward order.
|
||||
// The behavior of Do is undefined if f changes *r.
|
||||
//read note 对环上的所有元素进行函数操作,但是这边的操作好像是没办法改变环中的元素的,只能进行提取或者输出
|
||||
func (r *Ring) Do(f func(interface{})) {
|
||||
if r != nil {
|
||||
f(r.Value)
|
||||
for p := r.Next(); p != r; p = p.next {
|
||||
f(p.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/2 15:39
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testHeap
|
||||
|
||||
type Person struct {
|
||||
Name string //名字
|
||||
Age int //年龄
|
||||
Money float64 //身价
|
||||
}
|
||||
|
||||
type heapTool []*Person
|
||||
|
||||
func (h *heapTool) Less(i, j int) bool {
|
||||
return (*h)[i].Age < (*h)[j].Age
|
||||
}
|
||||
|
||||
func (h *heapTool) Swap(i, j int) {
|
||||
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
|
||||
}
|
||||
|
||||
func (h *heapTool) Len() int {
|
||||
return len(*h)
|
||||
}
|
||||
|
||||
func (h *heapTool) Pop() (v interface{}) {
|
||||
*h, v = (*h)[:h.Len()-1], (*h)[h.Len()-1]
|
||||
return
|
||||
}
|
||||
|
||||
func (h *heapTool) Push(v interface{}) {
|
||||
*h = append(*h, v.(*Person))
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/2 11:19
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testHeap
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHeapTool(t *testing.T) {
|
||||
personList := new(heapTool)
|
||||
personList.Push(&Person{
|
||||
Name: "小明",
|
||||
Age: 20,
|
||||
Money: 100000.99,
|
||||
})
|
||||
personList.Push(&Person{
|
||||
Name: "小施",
|
||||
Age: 30,
|
||||
Money: 1002341.99,
|
||||
})
|
||||
|
||||
personList.Push(&Person{
|
||||
Name: "小康",
|
||||
Age: 10,
|
||||
Money: 200.99,
|
||||
})
|
||||
|
||||
personList.Push(&Person{
|
||||
Name: "老施",
|
||||
Age: 50,
|
||||
Money: 10343240000.99,
|
||||
})
|
||||
|
||||
personList.Push(&Person{
|
||||
Name: "老康",
|
||||
Age: 70,
|
||||
Money: 10340.99,
|
||||
})
|
||||
personList.Push(&Person{
|
||||
Name: "老明",
|
||||
Age: 80,
|
||||
Money: 13240000.99,
|
||||
})
|
||||
personList.Push(&Person{
|
||||
Name: "老林",
|
||||
Age: 90,
|
||||
Money: 10340000.99,
|
||||
})
|
||||
|
||||
heap.Init(personList)
|
||||
for personList.Len() > 0 {
|
||||
pop := heap.Pop(personList)
|
||||
fmt.Println(fmt.Sprintf("%v,%v岁,资产:%v", pop.(*Person).Name, pop.(*Person).Age, pop.(*Person).Money))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/3 21:58
|
||||
* @Description:测试list.go包的元素
|
||||
*/
|
||||
|
||||
package testHeap
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
list2 := list.New()
|
||||
|
||||
list2.PushFront(Person{
|
||||
Name: "康康",
|
||||
Age: 10,
|
||||
Money: 20,
|
||||
})
|
||||
|
||||
list2.PushBack(Person{
|
||||
Name: "老施",
|
||||
Age: 40,
|
||||
Money: 1000000,
|
||||
})
|
||||
for e := list2.Front(); e != nil; e = e.Next() {
|
||||
value := e.Value.(Person)
|
||||
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
|
||||
frontE := list2.Front()
|
||||
list2.MoveToBack(frontE)
|
||||
|
||||
for e := list2.Front(); e != nil; e = e.Next() {
|
||||
value := e.Value.(Person)
|
||||
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
|
||||
xiaozhang := list2.InsertBefore(Person{
|
||||
Name: "小张",
|
||||
Age: 10,
|
||||
Money: 50,
|
||||
}, list2.Front())
|
||||
for e := list2.Front(); e != nil; e = e.Next() {
|
||||
value := e.Value.(Person)
|
||||
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
|
||||
list2.Remove(xiaozhang)
|
||||
for e := list2.Front(); e != nil; e = e.Next() {
|
||||
value := e.Value.(Person)
|
||||
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", value.Name, value.Age, value.Money))
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
|
||||
front := list2.Front().Value.(Person)
|
||||
fmt.Println(fmt.Sprintf("名字:%v,年龄:%v,身家:%v", front.Name, front.Age, front.Money))
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/7 9:33
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testHeap
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRing(t *testing.T) {
|
||||
fmt.Println("测试空链表")
|
||||
var rings ring.Ring
|
||||
fmt.Println(rings.Next().Value)
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("")
|
||||
|
||||
fmt.Println("测试环")
|
||||
newRing := makeN(10)
|
||||
Print(newRing)
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("")
|
||||
fmt.Println("测试环Link自己")
|
||||
newRing = makeN(10)
|
||||
newRing = newRing.Link(newRing)
|
||||
Print(newRing)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
fmt.Println("测试环Link其他环")
|
||||
newRing = makeN(10)
|
||||
newRing1 := makeN(5)
|
||||
newRing = newRing.Link(newRing1)
|
||||
Print(newRing)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
fmt.Println("测试Move")
|
||||
newRing1 = makeN(5)
|
||||
newRing1 = newRing1.Move(3)
|
||||
Print(newRing1)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
fmt.Println("测试Do,没有办法改变环中的元素.")
|
||||
newRing1 = makeN(5)
|
||||
newRing1.Do(func(i interface{}) {
|
||||
i = i.(int) + 1000
|
||||
})
|
||||
Print(newRing1)
|
||||
|
||||
newRingx := ring.New(3)
|
||||
for i := 1; i <= newRingx.Len(); i++ {
|
||||
newRingx.Value = Person{
|
||||
Name: "小明",
|
||||
Age: 100,
|
||||
Money: 19999,
|
||||
}
|
||||
newRingx = newRingx.Next()
|
||||
newRingx.Value = Person{
|
||||
Name: "小康",
|
||||
Age: 50,
|
||||
Money: 19921999,
|
||||
}
|
||||
newRingx = newRingx.Next()
|
||||
newRingx.Value = Person{
|
||||
Name: "小施",
|
||||
Age: 20,
|
||||
Money: 1111999,
|
||||
}
|
||||
}
|
||||
fmt.Println("测试Do,只能对元素进行提取或者输出.")
|
||||
s := make([]int, 0)
|
||||
newRingx.Do(func(i interface{}) {
|
||||
s = append(s, i.(Person).Age)
|
||||
})
|
||||
for _, item := range s {
|
||||
fmt.Println(item)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
fmt.Println("测试Unlink")
|
||||
newRing1 = makeN(5)
|
||||
newRing2 := newRing1.Unlink(2)
|
||||
Print(newRing1)
|
||||
println()
|
||||
fmt.Println("如果用赋值语句,可以返回被移除的元素")
|
||||
Print(newRing2)
|
||||
}
|
||||
|
||||
func Print(r *ring.Ring) {
|
||||
i, n := 0, r.Len()
|
||||
for p := r; i < n; p = p.Next() {
|
||||
fmt.Println(fmt.Sprintf("当前元素是%v", p.Value))
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
//创造对应的链表
|
||||
func makeN(n int) *ring.Ring {
|
||||
r := ring.New(n)
|
||||
for i := 1; i <= n; i++ {
|
||||
r.Value = i
|
||||
r = r.Next()
|
||||
}
|
||||
return r
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
@@ -0,0 +1,5 @@
|
||||
我的分析文章可参考:[Go源码解析:validator.v8](https://blog.csdn.net/qq_34326321/article/details/111030128)
|
||||
|
||||
流程图: 
|
||||
|
||||
流程图地址:[地址](https://www.processon.com/view/link/5fd6dabb63768906e6db0d25)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.test
|
||||
*.out
|
||||
*.txt
|
||||
cover.html
|
||||
README.html
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dean Karn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
Package validator
|
||||
================
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png">[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
[](https://semaphoreci.com/joeybloggs/validator)
|
||||
[](https://coveralls.io/github/go-playground/validator?branch=v8)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/validator)
|
||||
[](https://godoc.org/gopkg.in/go-playground/validator.v8)
|
||||

|
||||
|
||||
Package validator implements value validations for structs and individual fields based on tags.
|
||||
|
||||
It has the following **unique** features:
|
||||
|
||||
- Cross Field and Cross Struct validations by using validation tags or custom validators.
|
||||
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
|
||||
- Handles type interface by determining it's underlying type prior to validation.
|
||||
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
|
||||
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
|
||||
- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use go get.
|
||||
|
||||
go get gopkg.in/go-playground/validator.v8
|
||||
|
||||
or to update
|
||||
|
||||
go get -u gopkg.in/go-playground/validator.v8
|
||||
|
||||
Then import the validator package into your own code.
|
||||
|
||||
import "gopkg.in/go-playground/validator.v8"
|
||||
|
||||
Error Return Value
|
||||
-------
|
||||
|
||||
Validation functions return type error
|
||||
|
||||
They return type error to avoid the issue discussed in the following, where err is always != nil:
|
||||
|
||||
* http://stackoverflow.com/a/29138676/3158232
|
||||
* https://github.com/go-playground/validator/issues/134
|
||||
|
||||
validator only returns nil or ValidationErrors as type error; so in you code all you need to do
|
||||
is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors
|
||||
like so:
|
||||
|
||||
```go
|
||||
err := validate.Struct(mystruct)
|
||||
validationErrors := err.(validator.ValidationErrors)
|
||||
```
|
||||
|
||||
Usage and documentation
|
||||
------
|
||||
|
||||
Please see http://godoc.org/gopkg.in/go-playground/validator.v8 for detailed usage docs.
|
||||
|
||||
##### Examples:
|
||||
|
||||
Struct & Field validation
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `validate:"required"`
|
||||
LastName string `validate:"required"`
|
||||
Age uint8 `validate:"gte=0,lte=130"`
|
||||
Email string `validate:"required,email"`
|
||||
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
|
||||
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
|
||||
}
|
||||
|
||||
// Address houses a users address information
|
||||
type Address struct {
|
||||
Street string `validate:"required"`
|
||||
City string `validate:"required"`
|
||||
Planet string `validate:"required"`
|
||||
Phone string `validate:"required"`
|
||||
}
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
|
||||
validateStruct()
|
||||
validateField()
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "Badger",
|
||||
LastName: "Smith",
|
||||
Age: 135,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
|
||||
// Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
|
||||
err := errs.(validator.ValidationErrors)["User.Addresses[0].City"]
|
||||
fmt.Println(err.Field) // output: City
|
||||
fmt.Println(err.Tag) // output: required
|
||||
fmt.Println(err.Kind) // output: string
|
||||
fmt.Println(err.Type) // output: string
|
||||
fmt.Println(err.Param) // output:
|
||||
fmt.Println(err.Value) // output:
|
||||
|
||||
// from here you can create your own error messages in whatever language you wish
|
||||
return
|
||||
}
|
||||
|
||||
// save user to database
|
||||
}
|
||||
|
||||
func validateField() {
|
||||
myEmail := "joeybloggs.gmail.com"
|
||||
|
||||
errs := validate.Field(myEmail, "required,email")
|
||||
|
||||
if errs != nil {
|
||||
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
|
||||
return
|
||||
}
|
||||
|
||||
// email ok, move on
|
||||
}
|
||||
```
|
||||
|
||||
Custom Field Type
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// DbBackedUser User struct
|
||||
type DbBackedUser struct {
|
||||
Name sql.NullString `validate:"required"`
|
||||
Age sql.NullInt64 `validate:"required"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate := validator.New(config)
|
||||
|
||||
// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
|
||||
validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
|
||||
|
||||
x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
|
||||
errs := validate.Struct(x)
|
||||
|
||||
if len(errs.(validator.ValidationErrors)) > 0 {
|
||||
fmt.Printf("Errs:\n%+v\n", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateValuer implements validator.CustomTypeFunc
|
||||
func ValidateValuer(field reflect.Value) interface{} {
|
||||
if valuer, ok := field.Interface().(driver.Valuer); ok {
|
||||
val, err := valuer.Value()
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
// handle the error how you want
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Struct Level Validation
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `json:"fname"`
|
||||
LastName string `json:"lname"`
|
||||
Age uint8 `validate:"gte=0,lte=130"`
|
||||
Email string `validate:"required,email"`
|
||||
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
|
||||
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
|
||||
}
|
||||
|
||||
// Address houses a users address information
|
||||
type Address struct {
|
||||
Street string `validate:"required"`
|
||||
City string `validate:"required"`
|
||||
Planet string `validate:"required"`
|
||||
Phone string `validate:"required"`
|
||||
}
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
validate.RegisterStructValidation(UserStructLevelValidation, User{})
|
||||
|
||||
validateStruct()
|
||||
}
|
||||
|
||||
// UserStructLevelValidation contains custom struct level validations that don't always
|
||||
// make sense at the field validation level. For Example this function validates that either
|
||||
// FirstName or LastName exist; could have done that with a custom field validation but then
|
||||
// would have had to add it to both fields duplicating the logic + overhead, this way it's
|
||||
// only validated once.
|
||||
//
|
||||
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
|
||||
// hooks right into validator and you can combine with validation tags and still have a
|
||||
// common error output format.
|
||||
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||
|
||||
user := structLevel.CurrentStruct.Interface().(User)
|
||||
|
||||
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
|
||||
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
|
||||
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
|
||||
}
|
||||
|
||||
// plus can to more, even with different tag than "fnameorlname"
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
City: "Unknown",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Age: 45,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
|
||||
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
|
||||
err := errs.(validator.ValidationErrors)["User.FirstName"]
|
||||
fmt.Println(err.Field) // output: FirstName
|
||||
fmt.Println(err.Tag) // output: fnameorlname
|
||||
fmt.Println(err.Kind) // output: string
|
||||
fmt.Println(err.Type) // output: string
|
||||
fmt.Println(err.Param) // output:
|
||||
fmt.Println(err.Value) // output:
|
||||
|
||||
// from here you can create your own error messages in whatever language you wish
|
||||
return
|
||||
}
|
||||
|
||||
// save user to database
|
||||
}
|
||||
```
|
||||
|
||||
Benchmarks
|
||||
------
|
||||
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.3 darwin/amd64
|
||||
```go
|
||||
PASS
|
||||
BenchmarkFieldSuccess-8 20000000 118 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFieldFailure-8 2000000 758 ns/op 432 B/op 4 allocs/op
|
||||
BenchmarkFieldDiveSuccess-8 500000 2471 ns/op 464 B/op 28 allocs/op
|
||||
BenchmarkFieldDiveFailure-8 500000 3172 ns/op 896 B/op 32 allocs/op
|
||||
BenchmarkFieldCustomTypeSuccess-8 5000000 300 ns/op 32 B/op 2 allocs/op
|
||||
BenchmarkFieldCustomTypeFailure-8 2000000 775 ns/op 432 B/op 4 allocs/op
|
||||
BenchmarkFieldOrTagSuccess-8 1000000 1122 ns/op 4 B/op 1 allocs/op
|
||||
BenchmarkFieldOrTagFailure-8 1000000 1167 ns/op 448 B/op 6 allocs/op
|
||||
BenchmarkStructLevelValidationSuccess-8 3000000 548 ns/op 160 B/op 5 allocs/op
|
||||
BenchmarkStructLevelValidationFailure-8 3000000 558 ns/op 160 B/op 5 allocs/op
|
||||
BenchmarkStructSimpleCustomTypeSuccess-8 2000000 623 ns/op 36 B/op 3 allocs/op
|
||||
BenchmarkStructSimpleCustomTypeFailure-8 1000000 1381 ns/op 640 B/op 9 allocs/op
|
||||
BenchmarkStructPartialSuccess-8 1000000 1036 ns/op 272 B/op 9 allocs/op
|
||||
BenchmarkStructPartialFailure-8 1000000 1734 ns/op 730 B/op 14 allocs/op
|
||||
BenchmarkStructExceptSuccess-8 2000000 888 ns/op 250 B/op 7 allocs/op
|
||||
BenchmarkStructExceptFailure-8 1000000 1036 ns/op 272 B/op 9 allocs/op
|
||||
BenchmarkStructSimpleCrossFieldSuccess-8 2000000 773 ns/op 80 B/op 4 allocs/op
|
||||
BenchmarkStructSimpleCrossFieldFailure-8 1000000 1487 ns/op 536 B/op 9 allocs/op
|
||||
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1261 ns/op 112 B/op 7 allocs/op
|
||||
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 2055 ns/op 576 B/op 12 allocs/op
|
||||
BenchmarkStructSimpleSuccess-8 3000000 519 ns/op 4 B/op 1 allocs/op
|
||||
BenchmarkStructSimpleFailure-8 1000000 1429 ns/op 640 B/op 9 allocs/op
|
||||
BenchmarkStructSimpleSuccessParallel-8 10000000 146 ns/op 4 B/op 1 allocs/op
|
||||
BenchmarkStructSimpleFailureParallel-8 2000000 551 ns/op 640 B/op 9 allocs/op
|
||||
BenchmarkStructComplexSuccess-8 500000 3269 ns/op 244 B/op 15 allocs/op
|
||||
BenchmarkStructComplexFailure-8 200000 8436 ns/op 3609 B/op 60 allocs/op
|
||||
BenchmarkStructComplexSuccessParallel-8 1000000 1024 ns/op 244 B/op 15 allocs/op
|
||||
BenchmarkStructComplexFailureParallel-8 500000 3536 ns/op 3609 B/op 60 allocs/op
|
||||
```
|
||||
|
||||
Complimentary Software
|
||||
----------------------
|
||||
|
||||
Here is a list of software that compliments using this library either pre or post validation.
|
||||
|
||||
* [form](https://github.com/go-playground/form) - Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values. Dual Array and Full map support.
|
||||
* [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags.
|
||||
|
||||
How to Contribute
|
||||
------
|
||||
|
||||
There will always be a development branch for each version i.e. `v1-development`. In order to contribute,
|
||||
please make your pull requests against those branches.
|
||||
|
||||
If the changes being proposed or requested are breaking changes, please create an issue, for discussion
|
||||
or create a pull request against the highest development branch for example this package has a
|
||||
v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet.
|
||||
|
||||
I strongly encourage everyone whom creates a custom validation function to contribute them and
|
||||
help make this package even better.
|
||||
|
||||
License
|
||||
------
|
||||
Distributed under MIT License, please see license file in code for more details.
|
||||
+1429
File diff suppressed because it is too large
Load Diff
+285
@@ -0,0 +1,285 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type tagType uint8
|
||||
|
||||
const (
|
||||
typeDefault tagType = iota
|
||||
typeOmitEmpty
|
||||
typeNoStructLevel
|
||||
typeStructOnly
|
||||
typeDive
|
||||
typeOr
|
||||
typeExists
|
||||
)
|
||||
|
||||
type structCache struct {
|
||||
lock sync.Mutex
|
||||
m atomic.Value // map[reflect.Type]*cStruct
|
||||
}
|
||||
|
||||
func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
|
||||
c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *structCache) Set(key reflect.Type, value *cStruct) {
|
||||
|
||||
m := sc.m.Load().(map[reflect.Type]*cStruct)
|
||||
|
||||
nm := make(map[reflect.Type]*cStruct, len(m)+1)
|
||||
for k, v := range m {
|
||||
nm[k] = v
|
||||
}
|
||||
nm[key] = value
|
||||
sc.m.Store(nm)
|
||||
}
|
||||
|
||||
type tagCache struct {
|
||||
lock sync.Mutex
|
||||
m atomic.Value // map[string]*cTag
|
||||
}
|
||||
|
||||
func (tc *tagCache) Get(key string) (c *cTag, found bool) {
|
||||
c, found = tc.m.Load().(map[string]*cTag)[key]
|
||||
return
|
||||
}
|
||||
|
||||
func (tc *tagCache) Set(key string, value *cTag) {
|
||||
|
||||
m := tc.m.Load().(map[string]*cTag)
|
||||
|
||||
nm := make(map[string]*cTag, len(m)+1)
|
||||
for k, v := range m {
|
||||
nm[k] = v
|
||||
}
|
||||
nm[key] = value
|
||||
tc.m.Store(nm)
|
||||
}
|
||||
|
||||
type cStruct struct {
|
||||
Name string //结构体名称
|
||||
fields map[int]*cField //结构体对应的字段map
|
||||
fn StructLevelFunc //结构体校验器 (结构体类型->结构体校验器)
|
||||
}
|
||||
|
||||
type cField struct {
|
||||
Idx int //字段下标
|
||||
Name string //字段名
|
||||
AltName string //
|
||||
cTags *cTag //Field对应的cTag规则,是一个链表(一串的规则).
|
||||
}
|
||||
|
||||
type cTag struct {
|
||||
tag string //标签
|
||||
aliasTag string //
|
||||
actualAliasTag string //
|
||||
param string //如果是比较类型的标签,这里存放的是比较的值,比如说 min=10,这里存放的是【10】这个值
|
||||
hasAlias bool //是否有别名校验器标签
|
||||
typeof tagType //对应的tagType
|
||||
hasTag bool //是否存在tag标签
|
||||
fn Func //当前cTag对应的【tag标签校验器】
|
||||
next *cTag //下一个cTag标签
|
||||
}
|
||||
|
||||
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
|
||||
|
||||
v.structCache.lock.Lock()
|
||||
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
|
||||
|
||||
typ := current.Type()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures struct
|
||||
// isn't parsed again.
|
||||
//read note 从缓存里面获取对应结构体类型的处理.
|
||||
cs, ok := v.structCache.Get(typ)
|
||||
if ok {
|
||||
return cs
|
||||
}
|
||||
|
||||
//read note 如果缓存里面拿不到的话,就要从结构的Field中去解析
|
||||
cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]}
|
||||
|
||||
numFields := current.NumField()
|
||||
|
||||
var ctag *cTag
|
||||
var fld reflect.StructField
|
||||
var tag string
|
||||
var customName string
|
||||
|
||||
//read note 进行字段的处理
|
||||
for i := 0; i < numFields; i++ {
|
||||
|
||||
fld = typ.Field(i)
|
||||
|
||||
//read note 内嵌类型和小写的处理,直接pass
|
||||
// 类似这种的:
|
||||
/* type Derive struct {
|
||||
Base ---内嵌
|
||||
}
|
||||
*/
|
||||
if !fld.Anonymous && fld.PkgPath != blank {
|
||||
continue
|
||||
}
|
||||
|
||||
//read note 获取tag(通过之前Config设置的【tagName】属性去获取对应的tag)
|
||||
tag = fld.Tag.Get(v.tagName)
|
||||
|
||||
//read note 如果是忽略【-】的话,跳过
|
||||
if tag == skipValidationTag {
|
||||
continue
|
||||
}
|
||||
|
||||
customName = fld.Name
|
||||
|
||||
//read note config中【fieldNameTag】的处理,会设置到【cField】的【AltName】字段上,应该只是错误输出用的
|
||||
if v.fieldNameTag != blank {
|
||||
|
||||
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
|
||||
|
||||
// dash check is for json "-" (aka skipValidationTag) means don't output in json
|
||||
if name != "" && name != skipValidationTag {
|
||||
customName = name
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
|
||||
// and so only struct level caching can be used instead of combined with Field tag caching
|
||||
|
||||
//read note struct层的校验规则和Field层的校验规则不能共用(标签可能相同,但是别名会不一样..)
|
||||
|
||||
if len(tag) > 0 {
|
||||
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, blank, false)
|
||||
} else {
|
||||
// even if field doesn't have validations need cTag for traversing to potential inner/nested
|
||||
// elements of the field.
|
||||
ctag = new(cTag)
|
||||
}
|
||||
|
||||
cs.fields[i] = &cField{Idx: i, Name: fld.Name, AltName: customName, cTags: ctag}
|
||||
}
|
||||
|
||||
v.structCache.Set(typ, cs)
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
|
||||
|
||||
var t string
|
||||
var ok bool
|
||||
noAlias := len(alias) == 0
|
||||
//read note:这边会把tag根据【,】进行分割处理,得到对应的tag组,所以我们在写的时候可以写入,分割的标签
|
||||
tags := strings.Split(tag, tagSeparator)
|
||||
|
||||
for i := 0; i < len(tags); i++ {
|
||||
|
||||
t = tags[i]
|
||||
|
||||
if noAlias {
|
||||
alias = t
|
||||
}
|
||||
|
||||
//read note 如果有别名校验器,则回去查找别名校验器(第一个和后面的设置不一样.调用next和没有调用next的区别)
|
||||
if v.hasAliasValidators {
|
||||
// check map for alias and process new tags, otherwise process as usual
|
||||
if tagsVal, found := v.aliasValidators[t]; found {
|
||||
|
||||
if i == 0 {
|
||||
//read note 对别名校验器进行解析(一个子递归的过程.)除第一个校验器外,后面的校验器是通过一个next指向的链表连接起来
|
||||
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
|
||||
} else {
|
||||
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
|
||||
current.next, current = next, curr
|
||||
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//read note 设置对应的默认标签(第一个和后面的设置不一样.调用next和没有调用next的区别)
|
||||
if i == 0 {
|
||||
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
|
||||
firstCtag = current
|
||||
} else {
|
||||
current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
|
||||
current = current.next
|
||||
}
|
||||
|
||||
//read note 判断用 【,】分割后的标签,前面几个是处理特殊标签的.如果不是特殊标签,则往下处理设置tag的值
|
||||
switch t {
|
||||
case diveTag:
|
||||
current.typeof = typeDive
|
||||
continue
|
||||
|
||||
case omitempty:
|
||||
current.typeof = typeOmitEmpty
|
||||
continue
|
||||
|
||||
case structOnlyTag:
|
||||
current.typeof = typeStructOnly
|
||||
continue
|
||||
|
||||
case noStructLevelTag:
|
||||
current.typeof = typeNoStructLevel
|
||||
continue
|
||||
|
||||
case existsTag:
|
||||
current.typeof = typeExists
|
||||
continue
|
||||
|
||||
default:
|
||||
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
|
||||
orVals := strings.Split(t, orSeparator)
|
||||
|
||||
//read note 或的条件进行拆分.
|
||||
|
||||
for j := 0; j < len(orVals); j++ {
|
||||
//read note 解析【=】标签
|
||||
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
|
||||
|
||||
if noAlias {
|
||||
alias = vals[0]
|
||||
current.aliasTag = alias
|
||||
} else {
|
||||
current.actualAliasTag = t
|
||||
}
|
||||
|
||||
//read note 如果或的标签成立,也就是说 A|B|C ABC会组装成一个链(非头节点需要往下一个节点去处理,所以这边需要把current指向它的next)
|
||||
if j > 0 {
|
||||
current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
|
||||
current = current.next
|
||||
}
|
||||
|
||||
current.tag = vals[0]
|
||||
if len(current.tag) == 0 {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
|
||||
}
|
||||
|
||||
//read note 查找对应的校验规则
|
||||
if current.fn, ok = v.validationFuncs[current.tag]; !ok {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
|
||||
}
|
||||
|
||||
//read note 设置为typeOr.这个应该在后面处理对应字段的时候会拿到并且通过or的方式进行处理
|
||||
if len(orVals) > 1 {
|
||||
current.typeof = typeOr
|
||||
}
|
||||
|
||||
if len(vals) > 1 {
|
||||
current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+852
@@ -0,0 +1,852 @@
|
||||
/*
|
||||
Package validator implements value validations for structs and individual fields
|
||||
based on tags.
|
||||
|
||||
It can also handle Cross-Field and Cross-Struct validation for nested structs
|
||||
and has the ability to dive into arrays and maps of any type.
|
||||
|
||||
Why not a better error message?
|
||||
Because this library intends for you to handle your own error messages.
|
||||
|
||||
Why should I handle my own errors?
|
||||
Many reasons. We built an internationalized application and needed to know the
|
||||
field, and what validation failed so we could provide a localized error.
|
||||
|
||||
if fieldErr.Field == "Name" {
|
||||
switch fieldErr.ErrorTag
|
||||
case "required":
|
||||
return "Translated string based on field + error"
|
||||
default:
|
||||
return "Translated string based on field"
|
||||
}
|
||||
|
||||
|
||||
Validation Functions Return Type error
|
||||
|
||||
Doing things this way is actually the way the standard library does, see the
|
||||
file.Open method here:
|
||||
|
||||
https://golang.org/pkg/os/#Open.
|
||||
|
||||
The authors return type "error" to avoid the issue discussed in the following,
|
||||
where err is always != nil:
|
||||
|
||||
http://stackoverflow.com/a/29138676/3158232
|
||||
https://github.com/go-playground/validator/issues/134
|
||||
|
||||
Validator only returns nil or ValidationErrors as type error; so, in your code
|
||||
all you need to do is check if the error returned is not nil, and if it's not
|
||||
type cast it to type ValidationErrors like so err.(validator.ValidationErrors).
|
||||
|
||||
Custom Functions
|
||||
|
||||
Custom functions can be added. Example:
|
||||
|
||||
// Structure
|
||||
func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
|
||||
|
||||
if whatever {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
validate.RegisterValidation("custom tag name", customFunc)
|
||||
// NOTES: using the same tag name as an existing function
|
||||
// will overwrite the existing one
|
||||
|
||||
Cross-Field Validation
|
||||
|
||||
Cross-Field Validation can be done via the following tags:
|
||||
- eqfield
|
||||
- nefield
|
||||
- gtfield
|
||||
- gtefield
|
||||
- ltfield
|
||||
- ltefield
|
||||
- eqcsfield
|
||||
- necsfield
|
||||
- gtcsfield
|
||||
- ftecsfield
|
||||
- ltcsfield
|
||||
- ltecsfield
|
||||
|
||||
If, however, some custom cross-field validation is required, it can be done
|
||||
using a custom validation.
|
||||
|
||||
Why not just have cross-fields validation tags (i.e. only eqcsfield and not
|
||||
eqfield)?
|
||||
|
||||
The reason is efficiency. If you want to check a field within the same struct
|
||||
"eqfield" only has to find the field on the same struct (1 level). But, if we
|
||||
used "eqcsfield" it could be multiple levels down. Example:
|
||||
|
||||
type Inner struct {
|
||||
StartDate time.Time
|
||||
}
|
||||
|
||||
type Outer struct {
|
||||
InnerStructField *Inner
|
||||
CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"`
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
inner := &Inner{
|
||||
StartDate: now,
|
||||
}
|
||||
|
||||
outer := &Outer{
|
||||
InnerStructField: inner,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
errs := validate.Struct(outer)
|
||||
|
||||
// NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed
|
||||
// into the function
|
||||
// when calling validate.FieldWithValue(val, field, tag) val will be
|
||||
// whatever you pass, struct, field...
|
||||
// when calling validate.Field(field, tag) val will be nil
|
||||
|
||||
Multiple Validators
|
||||
|
||||
Multiple validators on a field will process in the order defined. Example:
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"max=10,min=1"`
|
||||
}
|
||||
|
||||
// max will be checked then min
|
||||
|
||||
Bad Validator definitions are not handled by the library. Example:
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"min=10,max=0"`
|
||||
}
|
||||
|
||||
// this definition of min max will never succeed
|
||||
|
||||
Using Validator Tags
|
||||
|
||||
Baked In Cross-Field validation only compares fields on the same struct.
|
||||
If Cross-Field + Cross-Struct validation is needed you should implement your
|
||||
own custom validator.
|
||||
|
||||
Comma (",") is the default separator of validation tags. If you wish to
|
||||
have a comma included within the parameter (i.e. excludesall=,) you will need to
|
||||
use the UTF-8 hex representation 0x2C, which is replaced in the code as a comma,
|
||||
so the above will become excludesall=0x2C.
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"excludesall=,"` // BAD! Do not include a comma.
|
||||
Field `validate:"excludesall=0x2C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
Pipe ("|") is the default separator of validation tags. If you wish to
|
||||
have a pipe included within the parameter i.e. excludesall=| you will need to
|
||||
use the UTF-8 hex representation 0x7C, which is replaced in the code as a pipe,
|
||||
so the above will become excludesall=0x7C
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"excludesall=|"` // BAD! Do not include a a pipe!
|
||||
Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
|
||||
Baked In Validators and Tags
|
||||
|
||||
Here is a list of the current built in validators:
|
||||
|
||||
|
||||
Skip Field
|
||||
|
||||
Tells the validation to skip this struct field; this is particularly
|
||||
handy in ignoring embedded structs from being validated. (Usage: -)
|
||||
Usage: -
|
||||
|
||||
|
||||
Or Operator
|
||||
|
||||
This is the 'or' operator allowing multiple validators to be used and
|
||||
accepted. (Usage: rbg|rgba) <-- this would allow either rgb or rgba
|
||||
colors to be accepted. This can also be combined with 'and' for example
|
||||
( Usage: omitempty,rgb|rgba)
|
||||
|
||||
Usage: |
|
||||
|
||||
StructOnly
|
||||
|
||||
When a field that is a nested struct is encountered, and contains this flag
|
||||
any validation on the nested struct will be run, but none of the nested
|
||||
struct fields will be validated. This is usefull if inside of you program
|
||||
you know the struct will be valid, but need to verify it has been assigned.
|
||||
NOTE: only "required" and "omitempty" can be used on a struct itself.
|
||||
|
||||
Usage: structonly
|
||||
|
||||
NoStructLevel
|
||||
|
||||
Same as structonly tag except that any struct level validations will not run.
|
||||
|
||||
Usage: nostructlevel
|
||||
|
||||
Exists
|
||||
|
||||
Is a special tag without a validation function attached. It is used when a field
|
||||
is a Pointer, Interface or Invalid and you wish to validate that it exists.
|
||||
Example: want to ensure a bool exists if you define the bool as a pointer and
|
||||
use exists it will ensure there is a value; couldn't use required as it would
|
||||
fail when the bool was false. exists will fail is the value is a Pointer, Interface
|
||||
or Invalid and is nil.
|
||||
|
||||
Usage: exists
|
||||
|
||||
Omit Empty
|
||||
|
||||
Allows conditional validation, for example if a field is not set with
|
||||
a value (Determined by the "required" validator) then other validation
|
||||
such as min or max won't run, but if a value is set validation will run.
|
||||
|
||||
Usage: omitempty
|
||||
|
||||
Dive
|
||||
|
||||
This tells the validator to dive into a slice, array or map and validate that
|
||||
level of the slice, array or map with the validation tags that follow.
|
||||
Multidimensional nesting is also supported, each level you wish to dive will
|
||||
require another dive tag.
|
||||
|
||||
Usage: dive
|
||||
|
||||
Example #1
|
||||
|
||||
[][]string with validation tag "gt=0,dive,len=1,dive,required"
|
||||
// gt=0 will be applied to []
|
||||
// len=1 will be applied to []string
|
||||
// required will be applied to string
|
||||
|
||||
Example #2
|
||||
|
||||
[][]string with validation tag "gt=0,dive,dive,required"
|
||||
// gt=0 will be applied to []
|
||||
// []string will be spared validation
|
||||
// required will be applied to string
|
||||
|
||||
Required
|
||||
|
||||
This validates that the value is not the data types default zero value.
|
||||
For numbers ensures value is not zero. For strings ensures value is
|
||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||
ensures the value is not nil.
|
||||
|
||||
Usage: required
|
||||
|
||||
Length
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
equal to the parameter given. For strings, it checks that
|
||||
the string length is exactly that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: len=10
|
||||
|
||||
Maximum
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
less than or equal to the parameter given. For strings, it checks
|
||||
that the string length is at most that number of characters. For
|
||||
slices, arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: max=10
|
||||
|
||||
Mininum
|
||||
|
||||
For numbers, min will ensure that the value is
|
||||
greater or equal to the parameter given. For strings, it checks that
|
||||
the string length is at least that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: min=10
|
||||
|
||||
Equals
|
||||
|
||||
For strings & numbers, eq will ensure that the value is
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Usage: eq=10
|
||||
|
||||
Not Equal
|
||||
|
||||
For strings & numbers, ne will ensure that the value is not
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Usage: ne=10
|
||||
|
||||
Greater Than
|
||||
|
||||
For numbers, this will ensure that the value is greater than the
|
||||
parameter given. For strings, it checks that the string length
|
||||
is greater than that number of characters. For slices, arrays
|
||||
and maps it validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is greater than time.Now.UTC().
|
||||
|
||||
Usage: gt
|
||||
|
||||
Greater Than or Equal
|
||||
|
||||
Same as 'min' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gte=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is greater than or equal to time.Now.UTC().
|
||||
|
||||
Usage: gte
|
||||
|
||||
Less Than
|
||||
|
||||
For numbers, this will ensure that the value is less than the parameter given.
|
||||
For strings, it checks that the string length is less than that number of
|
||||
characters. For slices, arrays, and maps it validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: lt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
For time.Time ensures the time value is less than time.Now.UTC().
|
||||
|
||||
Usage: lt
|
||||
|
||||
Less Than or Equal
|
||||
|
||||
Same as 'max' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: lte=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is less than or equal to time.Now.UTC().
|
||||
|
||||
Usage: lte
|
||||
|
||||
Field Equals Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
a struct or passed in field.
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on Password field using:
|
||||
Usage: eqfield=ConfirmPassword
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(password, confirmpassword, "eqfield")
|
||||
|
||||
Field Equals Another Field (relative)
|
||||
|
||||
This does the same as eqfield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: eqcsfield=InnerStructField.Field)
|
||||
|
||||
Field Does Not Equal Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
a struct or passed in field.
|
||||
|
||||
Examples:
|
||||
|
||||
// Confirm two colors are not the same:
|
||||
//
|
||||
// Validation on Color field:
|
||||
Usage: nefield=Color2
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(color1, color2, "nefield")
|
||||
|
||||
Field Does Not Equal Another Field (relative)
|
||||
|
||||
This does the same as nefield except that it validates the field provided
|
||||
relative to the top level struct.
|
||||
|
||||
Usage: necsfield=InnerStructField.Field
|
||||
|
||||
Field Greater Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(gtfield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "gtfield")
|
||||
|
||||
|
||||
Field Greater Than Another Relative Field
|
||||
|
||||
This does the same as gtfield except that it validates the field provided
|
||||
relative to the top level struct.
|
||||
|
||||
Usage: gtcsfield=InnerStructField.Field
|
||||
|
||||
Field Greater Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(gtefield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "gtefield")
|
||||
|
||||
Field Greater Than or Equal To Another Relative Field
|
||||
|
||||
This does the same as gtefield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: gtecsfield=InnerStructField.Field
|
||||
|
||||
Less Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(ltfield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "ltfield")
|
||||
|
||||
Less Than Another Relative Field
|
||||
|
||||
This does the same as ltfield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: ltcsfield=InnerStructField.Field
|
||||
|
||||
Less Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(ltefield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "ltefield")
|
||||
|
||||
Less Than or Equal To Another Relative Field
|
||||
|
||||
This does the same as ltefield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: ltecsfield=InnerStructField.Field
|
||||
|
||||
Alpha Only
|
||||
|
||||
This validates that a string value contains alpha characters only
|
||||
|
||||
Usage: alpha
|
||||
|
||||
Alphanumeric
|
||||
|
||||
This validates that a string value contains alphanumeric characters only
|
||||
|
||||
Usage: alphanum
|
||||
|
||||
Numeric
|
||||
|
||||
This validates that a string value contains a basic numeric value.
|
||||
basic excludes exponents etc...
|
||||
|
||||
Usage: numeric
|
||||
|
||||
Hexadecimal String
|
||||
|
||||
This validates that a string value contains a valid hexadecimal.
|
||||
|
||||
Usage: hexadecimal
|
||||
|
||||
Hexcolor String
|
||||
|
||||
This validates that a string value contains a valid hex color including
|
||||
hashtag (#)
|
||||
|
||||
Usage: hexcolor
|
||||
|
||||
RGB String
|
||||
|
||||
This validates that a string value contains a valid rgb color
|
||||
|
||||
Usage: rgb
|
||||
|
||||
RGBA String
|
||||
|
||||
This validates that a string value contains a valid rgba color
|
||||
|
||||
Usage: rgba
|
||||
|
||||
HSL String
|
||||
|
||||
This validates that a string value contains a valid hsl color
|
||||
|
||||
Usage: hsl
|
||||
|
||||
HSLA String
|
||||
|
||||
This validates that a string value contains a valid hsla color
|
||||
|
||||
Usage: hsla
|
||||
|
||||
E-mail String
|
||||
|
||||
This validates that a string value contains a valid email
|
||||
This may not conform to all possibilities of any rfc standard, but neither
|
||||
does any email provider accept all posibilities.
|
||||
|
||||
Usage: email
|
||||
|
||||
URL String
|
||||
|
||||
This validates that a string value contains a valid url
|
||||
This will accept any url the golang request uri accepts but must contain
|
||||
a schema for example http:// or rtmp://
|
||||
|
||||
Usage: url
|
||||
|
||||
URI String
|
||||
|
||||
This validates that a string value contains a valid uri
|
||||
This will accept any uri the golang request uri accepts
|
||||
|
||||
Usage: uri
|
||||
|
||||
Base64 String
|
||||
|
||||
This validates that a string value contains a valid base64 value.
|
||||
Although an empty string is valid base64 this will report an empty string
|
||||
as an error, if you wish to accept an empty string as valid you can use
|
||||
this with the omitempty tag.
|
||||
|
||||
Usage: base64
|
||||
|
||||
Contains
|
||||
|
||||
This validates that a string value contains the substring value.
|
||||
|
||||
Usage: contains=@
|
||||
|
||||
Contains Any
|
||||
|
||||
This validates that a string value contains any Unicode code points
|
||||
in the substring value.
|
||||
|
||||
Usage: containsany=!@#?
|
||||
|
||||
Contains Rune
|
||||
|
||||
This validates that a string value contains the supplied rune value.
|
||||
|
||||
Usage: containsrune=@
|
||||
|
||||
Excludes
|
||||
|
||||
This validates that a string value does not contain the substring value.
|
||||
|
||||
Usage: excludes=@
|
||||
|
||||
Excludes All
|
||||
|
||||
This validates that a string value does not contain any Unicode code
|
||||
points in the substring value.
|
||||
|
||||
Usage: excludesall=!@#?
|
||||
|
||||
Excludes Rune
|
||||
|
||||
This validates that a string value does not contain the supplied rune value.
|
||||
|
||||
Usage: excludesrune=@
|
||||
|
||||
International Standard Book Number
|
||||
|
||||
This validates that a string value contains a valid isbn10 or isbn13 value.
|
||||
|
||||
Usage: isbn
|
||||
|
||||
International Standard Book Number 10
|
||||
|
||||
This validates that a string value contains a valid isbn10 value.
|
||||
|
||||
Usage: isbn10
|
||||
|
||||
International Standard Book Number 13
|
||||
|
||||
This validates that a string value contains a valid isbn13 value.
|
||||
|
||||
Usage: isbn13
|
||||
|
||||
|
||||
Universally Unique Identifier UUID
|
||||
|
||||
This validates that a string value contains a valid UUID.
|
||||
|
||||
Usage: uuid
|
||||
|
||||
Universally Unique Identifier UUID v3
|
||||
|
||||
This validates that a string value contains a valid version 3 UUID.
|
||||
|
||||
Usage: uuid3
|
||||
|
||||
Universally Unique Identifier UUID v4
|
||||
|
||||
This validates that a string value contains a valid version 4 UUID.
|
||||
|
||||
Usage: uuid4
|
||||
|
||||
Universally Unique Identifier UUID v5
|
||||
|
||||
This validates that a string value contains a valid version 5 UUID.
|
||||
|
||||
Usage: uuid5
|
||||
|
||||
ASCII
|
||||
|
||||
This validates that a string value contains only ASCII characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: ascii
|
||||
|
||||
Printable ASCII
|
||||
|
||||
This validates that a string value contains only printable ASCII characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: asciiprint
|
||||
|
||||
Multi-Byte Characters
|
||||
|
||||
This validates that a string value contains one or more multibyte characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: multibyte
|
||||
|
||||
Data URL
|
||||
|
||||
This validates that a string value contains a valid DataURI.
|
||||
NOTE: this will also validate that the data portion is valid base64
|
||||
|
||||
Usage: datauri
|
||||
|
||||
Latitude
|
||||
|
||||
This validates that a string value contains a valid latitude.
|
||||
|
||||
Usage: latitude
|
||||
|
||||
Longitude
|
||||
|
||||
This validates that a string value contains a valid longitude.
|
||||
|
||||
Usage: longitude
|
||||
|
||||
Social Security Number SSN
|
||||
|
||||
This validates that a string value contains a valid U.S. Social Security Number.
|
||||
|
||||
Usage: ssn
|
||||
|
||||
Internet Protocol Address IP
|
||||
|
||||
This validates that a string value contains a valid IP Adress.
|
||||
|
||||
Usage: ip
|
||||
|
||||
Internet Protocol Address IPv4
|
||||
|
||||
This validates that a string value contains a valid v4 IP Adress.
|
||||
|
||||
Usage: ipv4
|
||||
|
||||
Internet Protocol Address IPv6
|
||||
|
||||
This validates that a string value contains a valid v6 IP Adress.
|
||||
|
||||
Usage: ipv6
|
||||
|
||||
Classless Inter-Domain Routing CIDR
|
||||
|
||||
This validates that a string value contains a valid CIDR Adress.
|
||||
|
||||
Usage: cidr
|
||||
|
||||
Classless Inter-Domain Routing CIDRv4
|
||||
|
||||
This validates that a string value contains a valid v4 CIDR Adress.
|
||||
|
||||
Usage: cidrv4
|
||||
|
||||
Classless Inter-Domain Routing CIDRv6
|
||||
|
||||
This validates that a string value contains a valid v6 CIDR Adress.
|
||||
|
||||
Usage: cidrv6
|
||||
|
||||
Transmission Control Protocol Address TCP
|
||||
|
||||
This validates that a string value contains a valid resolvable TCP Adress.
|
||||
|
||||
Usage: tcp_addr
|
||||
|
||||
Transmission Control Protocol Address TCPv4
|
||||
|
||||
This validates that a string value contains a valid resolvable v4 TCP Adress.
|
||||
|
||||
Usage: tcp4_addr
|
||||
|
||||
Transmission Control Protocol Address TCPv6
|
||||
|
||||
This validates that a string value contains a valid resolvable v6 TCP Adress.
|
||||
|
||||
Usage: tcp6_addr
|
||||
|
||||
User Datagram Protocol Address UDP
|
||||
|
||||
This validates that a string value contains a valid resolvable UDP Adress.
|
||||
|
||||
Usage: udp_addr
|
||||
|
||||
User Datagram Protocol Address UDPv4
|
||||
|
||||
This validates that a string value contains a valid resolvable v4 UDP Adress.
|
||||
|
||||
Usage: udp4_addr
|
||||
|
||||
User Datagram Protocol Address UDPv6
|
||||
|
||||
This validates that a string value contains a valid resolvable v6 UDP Adress.
|
||||
|
||||
Usage: udp6_addr
|
||||
|
||||
Internet Protocol Address IP
|
||||
|
||||
This validates that a string value contains a valid resolvable IP Adress.
|
||||
|
||||
Usage: ip_addr
|
||||
|
||||
Internet Protocol Address IPv4
|
||||
|
||||
This validates that a string value contains a valid resolvable v4 IP Adress.
|
||||
|
||||
Usage: ip4_addr
|
||||
|
||||
Internet Protocol Address IPv6
|
||||
|
||||
This validates that a string value contains a valid resolvable v6 IP Adress.
|
||||
|
||||
Usage: ip6_addr
|
||||
|
||||
Unix domain socket end point Address
|
||||
|
||||
This validates that a string value contains a valid Unix Adress.
|
||||
|
||||
Usage: unix_addr
|
||||
|
||||
Media Access Control Address MAC
|
||||
|
||||
This validates that a string value contains a valid MAC Adress.
|
||||
|
||||
Usage: mac
|
||||
|
||||
Note: See Go's ParseMAC for accepted formats and types:
|
||||
|
||||
http://golang.org/src/net/mac.go?s=866:918#L29
|
||||
|
||||
Alias Validators and Tags
|
||||
|
||||
NOTE: When returning an error, the tag returned in "FieldError" will be
|
||||
the alias tag unless the dive tag is part of the alias. Everything after the
|
||||
dive tag is not reported as the alias tag. Also, the "ActualTag" in the before
|
||||
case will be the actual tag within the alias that failed.
|
||||
|
||||
Here is a list of the current built in alias tags:
|
||||
|
||||
"iscolor"
|
||||
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
|
||||
|
||||
Validator notes:
|
||||
|
||||
regex
|
||||
a regex validator won't be added because commas and = signs can be part
|
||||
of a regex which conflict with the validation definitions. Although
|
||||
workarounds can be made, they take away from using pure regex's.
|
||||
Furthermore it's quick and dirty but the regex's become harder to
|
||||
maintain and are not reusable, so it's as much a programming philosiphy
|
||||
as anything.
|
||||
|
||||
In place of this new validator functions should be created; a regex can
|
||||
be used within the validator function and even be precompiled for better
|
||||
efficiency within regexes.go.
|
||||
|
||||
And the best reason, you can submit a pull request and we can keep on
|
||||
adding to the validation library of this package!
|
||||
|
||||
Panics
|
||||
|
||||
This package panics when bad input is provided, this is by design, bad code like
|
||||
that should not make it to production.
|
||||
|
||||
type Test struct {
|
||||
TestField string `validate:"nonexistantfunction=1"`
|
||||
}
|
||||
|
||||
t := &Test{
|
||||
TestField: "Test"
|
||||
}
|
||||
|
||||
validate.Struct(t) // this will panic
|
||||
*/
|
||||
package validator
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+59
@@ -0,0 +1,59 @@
|
||||
package validator
|
||||
|
||||
import "regexp"
|
||||
|
||||
const (
|
||||
alphaRegexString = "^[a-zA-Z]+$"
|
||||
alphaNumericRegexString = "^[a-zA-Z0-9]+$"
|
||||
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
|
||||
numberRegexString = "^[0-9]+$"
|
||||
hexadecimalRegexString = "^[0-9a-fA-F]+$"
|
||||
hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
||||
rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
|
||||
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
|
||||
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
|
||||
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
|
||||
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
|
||||
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
aSCIIRegexString = "^[\x00-\x7F]*$"
|
||||
printableASCIIRegexString = "^[\x20-\x7E]*$"
|
||||
multibyteRegexString = "[^\x00-\x7F]"
|
||||
dataURIRegexString = "^data:.+\\/(.+);base64$"
|
||||
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
|
||||
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
|
||||
sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
|
||||
)
|
||||
|
||||
var (
|
||||
alphaRegex = regexp.MustCompile(alphaRegexString)
|
||||
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
|
||||
numericRegex = regexp.MustCompile(numericRegexString)
|
||||
numberRegex = regexp.MustCompile(numberRegexString)
|
||||
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
|
||||
hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
|
||||
rgbRegex = regexp.MustCompile(rgbRegexString)
|
||||
rgbaRegex = regexp.MustCompile(rgbaRegexString)
|
||||
hslRegex = regexp.MustCompile(hslRegexString)
|
||||
hslaRegex = regexp.MustCompile(hslaRegexString)
|
||||
emailRegex = regexp.MustCompile(emailRegexString)
|
||||
base64Regex = regexp.MustCompile(base64RegexString)
|
||||
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
|
||||
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
|
||||
uUID3Regex = regexp.MustCompile(uUID3RegexString)
|
||||
uUID4Regex = regexp.MustCompile(uUID4RegexString)
|
||||
uUID5Regex = regexp.MustCompile(uUID5RegexString)
|
||||
uUIDRegex = regexp.MustCompile(uUIDRegexString)
|
||||
aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
|
||||
printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
|
||||
multibyteRegex = regexp.MustCompile(multibyteRegexString)
|
||||
dataURIRegex = regexp.MustCompile(dataURIRegexString)
|
||||
latitudeRegex = regexp.MustCompile(latitudeRegexString)
|
||||
longitudeRegex = regexp.MustCompile(longitudeRegexString)
|
||||
sSNRegex = regexp.MustCompile(sSNRegexString)
|
||||
)
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
blank = ""
|
||||
namespaceSeparator = "."
|
||||
leftBracket = "["
|
||||
rightBracket = "]"
|
||||
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
|
||||
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
||||
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
||||
)
|
||||
|
||||
var (
|
||||
restrictedTags = map[string]struct{}{
|
||||
diveTag: {},
|
||||
existsTag: {},
|
||||
structOnlyTag: {},
|
||||
omitempty: {},
|
||||
skipValidationTag: {},
|
||||
utf8HexComma: {},
|
||||
utf8Pipe: {},
|
||||
noStructLevelTag: {},
|
||||
}
|
||||
)
|
||||
|
||||
// ExtractType gets the actual underlying type of field value.
|
||||
// It will dive into pointers, customTypes and return you the
|
||||
// underlying value and it's kind.
|
||||
// it is exposed for use within you Custom Functions
|
||||
func (v *Validate) ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) {
|
||||
|
||||
val, k, _ := v.extractTypeInternal(current, false)
|
||||
return val, k
|
||||
}
|
||||
|
||||
// only exists to not break backward compatibility, needed to return the third param for a bug fix internally
|
||||
func (v *Validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
|
||||
|
||||
switch current.Kind() {
|
||||
case reflect.Ptr:
|
||||
|
||||
nullable = true
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Ptr, nullable
|
||||
}
|
||||
|
||||
return v.extractTypeInternal(current.Elem(), nullable)
|
||||
|
||||
case reflect.Interface:
|
||||
|
||||
nullable = true
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Interface, nullable
|
||||
}
|
||||
|
||||
return v.extractTypeInternal(current.Elem(), nullable)
|
||||
|
||||
case reflect.Invalid:
|
||||
return current, reflect.Invalid, nullable
|
||||
|
||||
default:
|
||||
|
||||
if v.hasCustomFuncs {
|
||||
|
||||
if fn, ok := v.customTypeFuncs[current.Type()]; ok {
|
||||
//read note 如果有自定义的类型校验器,则这边会先去调用一下类型校验器,然后在返回对应的结构体值,继续往回上一层,往下校验
|
||||
return v.extractTypeInternal(reflect.ValueOf(fn(current)), nullable)
|
||||
}
|
||||
}
|
||||
|
||||
return current, current.Kind(), nullable
|
||||
}
|
||||
}
|
||||
|
||||
// GetStructFieldOK traverses a struct to retrieve a specific field denoted by the provided namespace and
|
||||
// returns the field, field kind and whether is was successful in retrieving the field at all.
|
||||
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
|
||||
// could not be retrieved because it didn't exist.
|
||||
func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
|
||||
|
||||
current, kind := v.ExtractType(current)
|
||||
|
||||
if kind == reflect.Invalid {
|
||||
return current, kind, false
|
||||
}
|
||||
|
||||
if namespace == blank {
|
||||
return current, kind, true
|
||||
}
|
||||
|
||||
switch kind {
|
||||
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
|
||||
return current, kind, false
|
||||
|
||||
case reflect.Struct:
|
||||
|
||||
typ := current.Type()
|
||||
fld := namespace
|
||||
ns := namespace
|
||||
|
||||
if typ != timeType && typ != timePtrType {
|
||||
|
||||
idx := strings.Index(namespace, namespaceSeparator)
|
||||
|
||||
if idx != -1 {
|
||||
fld = namespace[:idx]
|
||||
ns = namespace[idx+1:]
|
||||
} else {
|
||||
ns = blank
|
||||
}
|
||||
|
||||
bracketIdx := strings.Index(fld, leftBracket)
|
||||
if bracketIdx != -1 {
|
||||
fld = fld[:bracketIdx]
|
||||
|
||||
ns = namespace[bracketIdx:]
|
||||
}
|
||||
|
||||
current = current.FieldByName(fld)
|
||||
|
||||
return v.GetStructFieldOK(current, ns)
|
||||
}
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
idx := strings.Index(namespace, leftBracket)
|
||||
idx2 := strings.Index(namespace, rightBracket)
|
||||
|
||||
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
|
||||
|
||||
if arrIdx >= current.Len() {
|
||||
return current, kind, false
|
||||
}
|
||||
|
||||
startIdx := idx2 + 1
|
||||
|
||||
if startIdx < len(namespace) {
|
||||
if namespace[startIdx:startIdx+1] == namespaceSeparator {
|
||||
startIdx++
|
||||
}
|
||||
}
|
||||
|
||||
return v.GetStructFieldOK(current.Index(arrIdx), namespace[startIdx:])
|
||||
|
||||
case reflect.Map:
|
||||
idx := strings.Index(namespace, leftBracket) + 1
|
||||
idx2 := strings.Index(namespace, rightBracket)
|
||||
|
||||
endIdx := idx2
|
||||
|
||||
if endIdx+1 < len(namespace) {
|
||||
if namespace[endIdx+1:endIdx+2] == namespaceSeparator {
|
||||
endIdx++
|
||||
}
|
||||
}
|
||||
|
||||
key := namespace[idx:idx2]
|
||||
|
||||
switch current.Type().Key().Kind() {
|
||||
case reflect.Int:
|
||||
i, _ := strconv.Atoi(key)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Int8:
|
||||
i, _ := strconv.ParseInt(key, 10, 8)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:])
|
||||
case reflect.Int16:
|
||||
i, _ := strconv.ParseInt(key, 10, 16)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:])
|
||||
case reflect.Int32:
|
||||
i, _ := strconv.ParseInt(key, 10, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:])
|
||||
case reflect.Int64:
|
||||
i, _ := strconv.ParseInt(key, 10, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Uint:
|
||||
i, _ := strconv.ParseUint(key, 10, 0)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint8:
|
||||
i, _ := strconv.ParseUint(key, 10, 8)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint16:
|
||||
i, _ := strconv.ParseUint(key, 10, 16)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint32:
|
||||
i, _ := strconv.ParseUint(key, 10, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint64:
|
||||
i, _ := strconv.ParseUint(key, 10, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Float32:
|
||||
f, _ := strconv.ParseFloat(key, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:])
|
||||
case reflect.Float64:
|
||||
f, _ := strconv.ParseFloat(key, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:])
|
||||
case reflect.Bool:
|
||||
b, _ := strconv.ParseBool(key)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:])
|
||||
|
||||
// reflect.Type = string
|
||||
default:
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:])
|
||||
}
|
||||
}
|
||||
|
||||
// if got here there was more namespace, cannot go any deeper
|
||||
panic("Invalid field namespace")
|
||||
}
|
||||
|
||||
// asInt returns the parameter as a int64
|
||||
// or panics if it can't convert
|
||||
func asInt(param string) int64 {
|
||||
|
||||
i, err := strconv.ParseInt(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asUint returns the parameter as a uint64
|
||||
// or panics if it can't convert
|
||||
func asUint(param string) uint64 {
|
||||
|
||||
i, err := strconv.ParseUint(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asFloat returns the parameter as a float64
|
||||
// or panics if it can't convert
|
||||
func asFloat(param string) float64 {
|
||||
|
||||
i, err := strconv.ParseFloat(param, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func panicIf(err error) {
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
+829
@@ -0,0 +1,829 @@
|
||||
/**
|
||||
* Package validator
|
||||
*
|
||||
* MISC:
|
||||
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
|
||||
*
|
||||
*/
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
utf8HexComma = "0x2C" //read note 在tag里面代表【,】;需要这么书写,直接写【,】会被识别成分割符
|
||||
utf8Pipe = "0x7C" //read note 在tag里面代表【|】;需要这么书写,直接写【|】会被识别成条件符
|
||||
tagSeparator = "," //read note tag标签分割符
|
||||
orSeparator = "|" //read note tag判断条件【或】
|
||||
tagKeySeparator = "=" //read note tag里面需要等值 使用=
|
||||
|
||||
structOnlyTag = "structonly" //read note 结构体上有的tagType,因为结构体的特殊性,他在获取cTag的时候是从第二个开始的
|
||||
_ // 也就是说第二个才是生效的,所以当只书写structonly的时候,他会略过这个tag,那么必须跟着前面的一个标签才行,比如说 `valid="require,structonly"`,这样才会生效
|
||||
noStructLevelTag = "nostructlevel" // 同上
|
||||
|
||||
omitempty = "omitempty" //read note 如果字段未设值则忽略它
|
||||
skipValidationTag = "-" //read note 忽略字段
|
||||
diveTag = "dive" //read note 深入到slice, array or map 里面去校验里面的字段是否正确,每一层都需要多一个【div】来标识
|
||||
_ //read note 比如说 [][]string "gt=0,dive,dive,required" gt是校验[][]string长度,第一个div是校验[]string,第二个div是校验string
|
||||
existsTag = "exists" //read note 校验值存在即可(除非是nil则会报错),和require的区别是说,require对默认值也会报错
|
||||
|
||||
//read note 数组结构的名称组成
|
||||
arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
|
||||
mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
|
||||
|
||||
//read note 字段校验错误信息打印
|
||||
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
|
||||
//read note 报错信息(非字段校验错误)
|
||||
invalidValidation = "Invalid validation tag on field %s"
|
||||
undefinedValidation = "Undefined validation function on field %s"
|
||||
validatorNotInitialized = "Validator instance not initialized"
|
||||
fieldNameRequired = "Field Name Required"
|
||||
tagRequired = "Tag Required"
|
||||
)
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
timePtrType = reflect.TypeOf(&time.Time{})
|
||||
defaultCField = new(cField)
|
||||
)
|
||||
|
||||
// StructLevel contains all of the information and helper methods
|
||||
// for reporting errors during struct level validation
|
||||
type StructLevel struct {
|
||||
TopStruct reflect.Value
|
||||
CurrentStruct reflect.Value
|
||||
errPrefix string
|
||||
nsPrefix string
|
||||
errs ValidationErrors
|
||||
v *Validate
|
||||
}
|
||||
|
||||
// ReportValidationErrors accepts the key relative to the top level struct and validatin errors.
|
||||
// Example: had a triple nested struct User, ContactInfo, Country and ran errs := validate.Struct(country)
|
||||
// from within a User struct level validation would call this method like so:
|
||||
// ReportValidationErrors("ContactInfo.", errs)
|
||||
// NOTE: relativeKey can contain both the Field Relative and Custom name relative paths
|
||||
// i.e. ReportValidationErrors("ContactInfo.|cInfo", errs) where cInfo represents say the JSON name of
|
||||
// the relative path; this will be split into 2 variables in the next valiator version.
|
||||
func (sl *StructLevel) ReportValidationErrors(relativeKey string, errs ValidationErrors) {
|
||||
for _, e := range errs {
|
||||
|
||||
idx := strings.Index(relativeKey, "|")
|
||||
var rel string
|
||||
var cRel string
|
||||
|
||||
if idx != -1 {
|
||||
rel = relativeKey[:idx]
|
||||
cRel = relativeKey[idx+1:]
|
||||
} else {
|
||||
rel = relativeKey
|
||||
}
|
||||
|
||||
key := sl.errPrefix + rel + e.Field
|
||||
|
||||
e.FieldNamespace = key
|
||||
e.NameNamespace = sl.nsPrefix + cRel + e.Name
|
||||
|
||||
sl.errs[key] = e
|
||||
}
|
||||
}
|
||||
|
||||
// ReportError reports an error just by passing the field and tag information
|
||||
// NOTE: tag can be an existing validation tag or just something you make up
|
||||
// and precess on the flip side it's up to you.
|
||||
func (sl *StructLevel) ReportError(field reflect.Value, fieldName string, customName string, tag string) {
|
||||
|
||||
field, kind := sl.v.ExtractType(field)
|
||||
|
||||
if fieldName == blank {
|
||||
panic(fieldNameRequired)
|
||||
}
|
||||
|
||||
if customName == blank {
|
||||
customName = fieldName
|
||||
}
|
||||
|
||||
if tag == blank {
|
||||
panic(tagRequired)
|
||||
}
|
||||
|
||||
ns := sl.errPrefix + fieldName
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
sl.errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: sl.nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: fieldName,
|
||||
Tag: tag,
|
||||
ActualTag: tag,
|
||||
Param: blank,
|
||||
Kind: kind,
|
||||
}
|
||||
default:
|
||||
sl.errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: sl.nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: fieldName,
|
||||
Tag: tag,
|
||||
ActualTag: tag,
|
||||
Param: blank,
|
||||
Value: field.Interface(),
|
||||
Kind: kind,
|
||||
Type: field.Type(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate contains the validator settings passed in using the Config struct
|
||||
type Validate struct {
|
||||
tagName string //校验起作用的tag名
|
||||
fieldNameTag string //
|
||||
validationFuncs map[string]Func //规则类型的校验 【tag标签】-> 校验规则
|
||||
structLevelFuncs map[reflect.Type]StructLevelFunc //规则结构体的校验 【结构体类型】-> 校验规则
|
||||
customTypeFuncs map[reflect.Type]CustomTypeFunc //类型校验器 【数据类型】-> 校验规则
|
||||
aliasValidators map[string]string //别名校验器 【别名匹配规则组合】-> 校验规则
|
||||
hasCustomFuncs bool //是否存在类型校验器
|
||||
hasAliasValidators bool //是否有别名校验器
|
||||
hasStructLevelFuncs bool //是否有结构体校验器
|
||||
tagCache *tagCache //tag对应的【校验规则方法】的缓存
|
||||
structCache *structCache //结构体对应的【校验规则方法】的缓存
|
||||
errsPool *sync.Pool //校验错误奖池
|
||||
}
|
||||
|
||||
func (v *Validate) initCheck() {
|
||||
if v == nil {
|
||||
panic(validatorNotInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
// Config contains the options that a Validator instance will use.
|
||||
// It is passed to the New() function
|
||||
type Config struct {
|
||||
TagName string
|
||||
FieldNameTag string
|
||||
}
|
||||
|
||||
// CustomTypeFunc allows for overriding or adding custom field type handler functions
|
||||
// field = field value of the type to return a value to be validated
|
||||
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
||||
type CustomTypeFunc func(field reflect.Value) interface{}
|
||||
|
||||
// Func accepts all values needed for file and cross field validation
|
||||
// v = validator instance, needed but some built in functions for it's custom types
|
||||
// topStruct = top level struct when validating by struct otherwise nil
|
||||
// currentStruct = current level struct when validating by struct otherwise optional comparison value
|
||||
// field = field value for validation
|
||||
// param = parameter used in validation i.e. gt=0 param would be 0
|
||||
type Func func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool
|
||||
|
||||
// StructLevelFunc accepts all values needed for struct level validation
|
||||
type StructLevelFunc func(v *Validate, structLevel *StructLevel)
|
||||
|
||||
// ValidationErrors is a type of map[string]*FieldError
|
||||
// it exists to allow for multiple errors to be passed from this library
|
||||
// and yet still subscribe to the error interface
|
||||
type ValidationErrors map[string]*FieldError
|
||||
|
||||
// Error is intended for use in development + debugging and not intended to be a production error message.
|
||||
// It allows ValidationErrors to subscribe to the Error interface.
|
||||
// All information to create an error message specific to your application is contained within
|
||||
// the FieldError found within the ValidationErrors map
|
||||
func (ve ValidationErrors) Error() string {
|
||||
|
||||
buff := bytes.NewBufferString(blank)
|
||||
|
||||
for key, err := range ve {
|
||||
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
|
||||
buff.WriteString("\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buff.String())
|
||||
}
|
||||
|
||||
// FieldError contains a single field's validation error along
|
||||
// with other properties that may be needed for error message creation
|
||||
type FieldError struct {
|
||||
FieldNamespace string
|
||||
NameNamespace string
|
||||
Field string
|
||||
Name string
|
||||
Tag string
|
||||
ActualTag string
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Param string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// New creates a new Validate instance for use.
|
||||
func New(config *Config) *Validate {
|
||||
|
||||
//read note tag校验器缓存,初始化
|
||||
tc := new(tagCache)
|
||||
tc.m.Store(make(map[string]*cTag))
|
||||
|
||||
//read note 结构体缓存,初始化一个Map,防止后面添加的时候报错
|
||||
sc := new(structCache)
|
||||
sc.m.Store(make(map[reflect.Type]*cStruct))
|
||||
|
||||
v := &Validate{
|
||||
tagName: config.TagName,
|
||||
fieldNameTag: config.FieldNameTag,
|
||||
tagCache: tc,
|
||||
structCache: sc,
|
||||
//read note 自定义一个错误池
|
||||
errsPool: &sync.Pool{New: func() interface{} {
|
||||
return ValidationErrors{}
|
||||
}}}
|
||||
|
||||
//read note 设置别名规则类(一组) 可参考: baked_in.go/bakedInAliasValidators
|
||||
if len(v.aliasValidators) == 0 {
|
||||
// must copy alias validators for separate validations to be used in each validator instance
|
||||
v.aliasValidators = map[string]string{}
|
||||
for k, val := range bakedInAliasValidators {
|
||||
//read note 默认别名校验器注册
|
||||
v.RegisterAliasValidation(k, val)
|
||||
}
|
||||
}
|
||||
|
||||
//read note 设置默认的校验方法.可参考: baked_in.go/bakedInValidators
|
||||
if len(v.validationFuncs) == 0 {
|
||||
// must copy validators for separate validations to be used in each instance
|
||||
v.validationFuncs = map[string]Func{}
|
||||
for k, val := range bakedInValidators {
|
||||
//read note 默认tag校验器注册
|
||||
v.RegisterValidation(k, val)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RegisterStructValidation registers a StructLevelFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) {
|
||||
v.initCheck()
|
||||
|
||||
if v.structLevelFuncs == nil {
|
||||
v.structLevelFuncs = map[reflect.Type]StructLevelFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
v.structLevelFuncs[reflect.TypeOf(t)] = fn
|
||||
fmt.Println(reflect.TypeOf(t))
|
||||
}
|
||||
|
||||
v.hasStructLevelFuncs = true
|
||||
}
|
||||
|
||||
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
|
||||
// NOTE: if the key already exists, the previous validation function will be replaced.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterValidation(key string, fn Func) error {
|
||||
v.initCheck()
|
||||
|
||||
if key == blank {
|
||||
return errors.New("Function Key cannot be empty")
|
||||
}
|
||||
|
||||
if fn == nil {
|
||||
return errors.New("Function cannot be empty")
|
||||
}
|
||||
|
||||
_, ok := restrictedTags[key]
|
||||
|
||||
if ok || strings.ContainsAny(key, restrictedTagChars) {
|
||||
panic(fmt.Sprintf(restrictedTagErr, key))
|
||||
}
|
||||
|
||||
v.validationFuncs[key] = fn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
//read note 注册类型的校验器,应该在校验之前就注册完成,因为该注册方法不是线程安全的.
|
||||
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
|
||||
v.initCheck()
|
||||
|
||||
if v.customTypeFuncs == nil {
|
||||
v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
v.customTypeFuncs[reflect.TypeOf(t)] = fn
|
||||
}
|
||||
|
||||
v.hasCustomFuncs = true
|
||||
}
|
||||
|
||||
// RegisterAliasValidation registers a mapping of a single validationstag that
|
||||
// defines a common or complex set of validation(s) to simplify adding validation
|
||||
// to structs. NOTE: when returning an error the tag returned in FieldError will be
|
||||
// the alias tag unless the dive tag is part of the alias; everything after the
|
||||
// dive tag is not reported as the alias tag. Also the ActualTag in the before case
|
||||
// will be the actual tag within the alias that failed.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterAliasValidation(alias, tags string) {
|
||||
v.initCheck()
|
||||
|
||||
_, ok := restrictedTags[alias]
|
||||
|
||||
if ok || strings.ContainsAny(alias, restrictedTagChars) {
|
||||
panic(fmt.Sprintf(restrictedAliasErr, alias))
|
||||
}
|
||||
|
||||
v.aliasValidators[alias] = tags
|
||||
v.hasAliasValidators = true
|
||||
}
|
||||
|
||||
// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
|
||||
// validate Array, Slice and maps fields which may contain more than one error
|
||||
//read note
|
||||
func (v *Validate) Field(field interface{}, tag string) error {
|
||||
v.initCheck()
|
||||
|
||||
if len(tag) == 0 || tag == skipValidationTag {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
fieldVal := reflect.ValueOf(field)
|
||||
|
||||
//read note 从tag缓存中获取 tag标签对应的校验方法
|
||||
ctag, ok := v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
//read note 加锁
|
||||
v.tagCache.lock.Lock()
|
||||
defer v.tagCache.lock.Unlock()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures tag
|
||||
// isn't parsed again.
|
||||
//read note 加锁之后在这边再获取一次,因为在上一次判断到加锁的过程中,可能有函数已经把对应方法加载进来了
|
||||
ctag, ok = v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
//read note
|
||||
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
|
||||
//read note 进行标签设值
|
||||
v.tagCache.Set(tag, ctag)
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
|
||||
|
||||
//read note 判断校验错误是否存在
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
|
||||
// validate Array, Slice and maps fields which may contain more than one error
|
||||
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
|
||||
v.initCheck()
|
||||
|
||||
if len(tag) == 0 || tag == skipValidationTag {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
topVal := reflect.ValueOf(val)
|
||||
|
||||
ctag, ok := v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
v.tagCache.lock.Lock()
|
||||
defer v.tagCache.lock.Unlock()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures tag
|
||||
// isn't parsed again.
|
||||
ctag, ok = v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
|
||||
v.tagCache.Set(tag, ctag)
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructPartial validates the fields passed in only, ignoring all others.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) StructPartial(current interface{}, fields ...string) error {
|
||||
v.initCheck()
|
||||
|
||||
sv, _ := v.ExtractType(reflect.ValueOf(current))
|
||||
name := sv.Type().Name()
|
||||
m := map[string]struct{}{}
|
||||
|
||||
if fields != nil {
|
||||
for _, k := range fields {
|
||||
|
||||
flds := strings.Split(k, namespaceSeparator)
|
||||
if len(flds) > 0 {
|
||||
|
||||
key := name + namespaceSeparator
|
||||
for _, s := range flds {
|
||||
|
||||
idx := strings.Index(s, leftBracket)
|
||||
|
||||
if idx != -1 {
|
||||
for idx != -1 {
|
||||
key += s[:idx]
|
||||
m[key] = struct{}{}
|
||||
|
||||
idx2 := strings.Index(s, rightBracket)
|
||||
idx2++
|
||||
key += s[idx:idx2]
|
||||
m[key] = struct{}{}
|
||||
s = s[idx2:]
|
||||
idx = strings.Index(s, leftBracket)
|
||||
}
|
||||
} else {
|
||||
|
||||
key += s
|
||||
m[key] = struct{}{}
|
||||
}
|
||||
|
||||
key += namespaceSeparator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
|
||||
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructExcept validates all fields except the ones passed in.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) StructExcept(current interface{}, fields ...string) error {
|
||||
v.initCheck()
|
||||
|
||||
sv, _ := v.ExtractType(reflect.ValueOf(current))
|
||||
name := sv.Type().Name()
|
||||
m := map[string]struct{}{}
|
||||
|
||||
for _, key := range fields {
|
||||
m[name+namespaceSeparator+key] = struct{}{}
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
|
||||
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
|
||||
// it returns nil or ValidationErrors as error.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) Struct(current interface{}) error {
|
||||
v.initCheck()
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
sv := reflect.ValueOf(current)
|
||||
|
||||
//read note 进行结构体校验
|
||||
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false)
|
||||
|
||||
//read note 校验之后,对校验错误进行处理.
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, isStructOnly bool) {
|
||||
|
||||
//read note 指针处理成结构体Value(返回其指向的实际结构体)
|
||||
if current.Kind() == reflect.Ptr && !current.IsNil() {
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
//read note 结构体类型校验.
|
||||
if current.Kind() != reflect.Struct && current.Kind() != reflect.Interface {
|
||||
panic("value passed for validation is not a struct")
|
||||
}
|
||||
|
||||
//read note 校验结构体
|
||||
v.tranverseStruct(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, useStructName, partial, exclude, includeExclude, nil, nil)
|
||||
}
|
||||
|
||||
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
|
||||
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, ct *cTag) {
|
||||
|
||||
var ok bool
|
||||
first := len(nsPrefix) == 0
|
||||
typ := current.Type()
|
||||
|
||||
//read note 结构体校验器_缓存
|
||||
cs, ok = v.structCache.Get(typ)
|
||||
if !ok {
|
||||
cs = v.extractStructCache(current, typ.Name())
|
||||
}
|
||||
|
||||
if useStructName {
|
||||
errPrefix += cs.Name + namespaceSeparator
|
||||
|
||||
if len(v.fieldNameTag) != 0 {
|
||||
nsPrefix += cs.Name + namespaceSeparator
|
||||
}
|
||||
}
|
||||
|
||||
// structonly tag present don't tranverseFields
|
||||
// but must still check and run below struct level validation
|
||||
// if present
|
||||
//read note 【structonly】标签会忽略掉Field的校验
|
||||
if first || ct == nil || ct.typeof != typeStructOnly {
|
||||
|
||||
for _, f := range cs.fields {
|
||||
|
||||
if partial {
|
||||
|
||||
_, ok = includeExclude[errPrefix+f.Name]
|
||||
|
||||
if (ok && exclude) || (!ok && !exclude) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(topStruct, currentStruct, current.Field(f.Idx), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, f, f.cTags)
|
||||
}
|
||||
}
|
||||
|
||||
// check if any struct level validations, after all field validations already checked.
|
||||
//read note 如果结构体层的校验存在的话,需要进行调用.(这边是规则类校验)
|
||||
if cs.fn != nil {
|
||||
cs.fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
|
||||
}
|
||||
}
|
||||
|
||||
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
|
||||
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, cf *cField, ct *cTag) {
|
||||
|
||||
//read note 处理Ptr、Interface、Invalid(validator自定义)、自定义结构体类型.
|
||||
// 返回参数为:值 、 数据源类型、nullable(用来校验 【结构体】的【omiEmpty】标签...)
|
||||
current, kind, nullable := v.extractTypeInternal(current, false)
|
||||
var typ reflect.Type
|
||||
|
||||
switch kind {
|
||||
//read note 处理地址、Interface、Invalid是直接往校验错误池中添加一个Field错误?
|
||||
case reflect.Ptr, reflect.Interface, reflect.Invalid:
|
||||
|
||||
if ct == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ct.typeof == typeOmitEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
if ct.hasTag {
|
||||
|
||||
ns := errPrefix + cf.Name
|
||||
|
||||
//read note 如果是Invalid,需要往校验错误池中添加一个Field错误
|
||||
if kind == reflect.Invalid {
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.tag,
|
||||
Param: ct.param,
|
||||
Kind: kind,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.tag,
|
||||
Param: ct.param,
|
||||
Value: current.Interface(),
|
||||
Kind: kind,
|
||||
Type: current.Type(),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
typ = current.Type()
|
||||
|
||||
if typ != timeType {
|
||||
|
||||
//read note 对于结构体,这边有一个比较特殊的处理就是cTag是从第二个位置开始,也就是之前一直尝试structonly不成功,就是需要在structonly前面加一个tag才行。
|
||||
// 前一个对于struct应该是不生效才对,第二个才会对struct生效
|
||||
if ct != nil {
|
||||
ct = ct.next
|
||||
}
|
||||
|
||||
if ct != nil && ct.typeof == typeNoStructLevel {
|
||||
return
|
||||
}
|
||||
|
||||
//read note 如果上面的cTag没有生效/没有tagType为【typeNoStructLevel】的标签.就直接进入这个结构体
|
||||
v.tranverseStruct(topStruct, current, current, errPrefix+cf.Name+namespaceSeparator, nsPrefix+cf.AltName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cs, ct)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !ct.hasTag {
|
||||
return
|
||||
}
|
||||
|
||||
typ = current.Type()
|
||||
|
||||
//read note 进行字段Field的校验,这边的话是一个for的循环,在不同的标签中去找到对应的处理方式
|
||||
OUTER:
|
||||
for {
|
||||
if ct == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch ct.typeof {
|
||||
|
||||
case typeExists:
|
||||
ct = ct.next
|
||||
continue
|
||||
|
||||
case typeOmitEmpty:
|
||||
|
||||
if !nullable && !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
|
||||
return
|
||||
}
|
||||
|
||||
ct = ct.next
|
||||
continue
|
||||
|
||||
case typeDive:
|
||||
|
||||
ct = ct.next
|
||||
|
||||
// traverse slice or map here
|
||||
// or panic ;)
|
||||
switch kind {
|
||||
//read note 【dive】标签处理Slice 和 Array:往数组中的每一个元素进行处理
|
||||
case reflect.Slice, reflect.Array:
|
||||
|
||||
for i := 0; i < current.Len(); i++ {
|
||||
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct)
|
||||
}
|
||||
//read note 【dive】标签处理 Map:往Map中的每一个元素进行处理
|
||||
case reflect.Map:
|
||||
for _, key := range current.MapKeys() {
|
||||
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct)
|
||||
}
|
||||
//read note 【div】只能标注在 Slice 、 Map 、Array 上,不然就是报错.
|
||||
default:
|
||||
// throw error, if not a slice or map then should not have gotten here
|
||||
// bad dive tag
|
||||
panic("dive error! can't dive on a non slice or map")
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case typeOr:
|
||||
|
||||
//read note 【|】的处理
|
||||
errTag := blank
|
||||
|
||||
for {
|
||||
|
||||
//read note 如果结果是true的话,因为【|】标签只要有一个成立就可以,所以可以直接排除掉其他 【|】标签了
|
||||
if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
|
||||
|
||||
// drain rest of the 'or' values, then continue or leave
|
||||
//read note 排除其他 【|】标签,直到下一个Field的非【|】标签再进入处理.
|
||||
for {
|
||||
|
||||
ct = ct.next
|
||||
|
||||
if ct == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ct.typeof != typeOr {
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errTag += orSeparator + ct.tag
|
||||
|
||||
//read note 没有下一个cTag的处理器.处理一i西安错误信息,返回.
|
||||
if ct.next == nil {
|
||||
// if we get here, no valid 'or' value and no more tags
|
||||
|
||||
ns := errPrefix + cf.Name
|
||||
|
||||
if ct.hasAlias {
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.actualAliasTag,
|
||||
Value: current.Interface(),
|
||||
Type: typ,
|
||||
Kind: kind,
|
||||
}
|
||||
} else {
|
||||
errs[errPrefix+cf.Name] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: errTag[1:],
|
||||
ActualTag: errTag[1:],
|
||||
Value: current.Interface(),
|
||||
Type: typ,
|
||||
Kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ct = ct.next
|
||||
}
|
||||
|
||||
default:
|
||||
//read note 剩下的标签处理.类似一个循环的处理
|
||||
if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
|
||||
|
||||
ns := errPrefix + cf.Name
|
||||
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.tag,
|
||||
Value: current.Interface(),
|
||||
Param: ct.param,
|
||||
Type: typ,
|
||||
Kind: kind,
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
ct = ct.next
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/11 16:16
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testValidator
|
||||
|
||||
type Ower struct {
|
||||
Age int `validate:"min=10"`
|
||||
}
|
||||
|
||||
type Cat struct {
|
||||
Age int `validate:"min=10"`
|
||||
Ower Ower
|
||||
}
|
||||
|
||||
type Man struct {
|
||||
Name string `validate:"ipe"`
|
||||
}
|
||||
|
||||
type Woman struct {
|
||||
Name string `validate:"diy=5"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
M map[string]int `validate:"min=10"`
|
||||
H int `validate:"max=20"`
|
||||
Exist bool `validate:"exists"`
|
||||
Require bool `validate:"required"`
|
||||
Cat Cat
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/11 16:16
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testValidator
|
||||
|
||||
import (
|
||||
"Go-Tool/SourceAnalysisAndTool/validator.v8/sourceAnalysis/gopkg.in/go-playground/validator.v8"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidator(t *testing.T) {
|
||||
fmt.Println("测试别名校验器")
|
||||
testAlias()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("测试默认和自定义tag校验器")
|
||||
testTag()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("测试字段校验器")
|
||||
testField()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("测试结构体校验器")
|
||||
testStruct()
|
||||
|
||||
}
|
||||
|
||||
func testStruct() {
|
||||
//添加结构体校验,对结构体进行处理之后,validator的标签还是会起作用
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
valid.RegisterStructValidation(func(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||
c := structLevel.CurrentStruct.Interface().(Cat)
|
||||
if c.Age < 100 {
|
||||
fmt.Println("猫的岁数不能小于100")
|
||||
}
|
||||
}, Cat{})
|
||||
p := Person{
|
||||
Name: "192.168.0.1",
|
||||
M: map[string]int{"A": 1},
|
||||
H: 2000,
|
||||
Exist: false,
|
||||
Require: false,
|
||||
Cat: Cat{
|
||||
Age: 0,
|
||||
Ower: Ower{},
|
||||
},
|
||||
}
|
||||
|
||||
err := valid.Struct(p)
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
func testField() {
|
||||
//如果注册了对应的Field,则会调用自定义的方法,不会再使用validator定义的标签
|
||||
//这边测试了结构体和基本类型,都是如上述描述的这么处理
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
valid.RegisterAliasValidation("ipe", "ip|ipv4|ipv6")
|
||||
|
||||
valid.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
|
||||
d := field.Interface().(Cat)
|
||||
if d.Age < 50 {
|
||||
fmt.Println("狗的岁数不能小于50")
|
||||
}
|
||||
return field
|
||||
}, Cat{})
|
||||
|
||||
p := Person{
|
||||
Name: "192.168.0.1",
|
||||
M: map[string]int{"A": 1},
|
||||
H: 2000,
|
||||
Exist: false,
|
||||
Require: false,
|
||||
Cat: Cat{
|
||||
Age: 0,
|
||||
Ower: Ower{},
|
||||
},
|
||||
}
|
||||
err := valid.Struct(p)
|
||||
fmt.Println(err)
|
||||
|
||||
valid.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
|
||||
d := field.Interface().(int)
|
||||
if d != 10 {
|
||||
fmt.Println("测试一下")
|
||||
}
|
||||
return field
|
||||
}, 1)
|
||||
|
||||
err1 := valid.Struct(p)
|
||||
fmt.Println(err1)
|
||||
|
||||
}
|
||||
|
||||
func testTag() {
|
||||
w := Woman{
|
||||
Name: "my name is Woman",
|
||||
}
|
||||
|
||||
w1 := Woman{
|
||||
Name: "my name is wo-man",
|
||||
}
|
||||
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
_ = valid.RegisterValidation("diy", func(v *validator.Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool {
|
||||
s := field.Interface().(string)
|
||||
|
||||
i, _ := strconv.ParseInt(param, 0, 64)
|
||||
if int64(len(s)) < i || strings.Contains(s, "Woman") {
|
||||
fmt.Println("校验一下")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
err := valid.Struct(w)
|
||||
fmt.Println(err)
|
||||
|
||||
err1 := valid.Struct(w1)
|
||||
fmt.Println(err1)
|
||||
}
|
||||
|
||||
func testAlias() {
|
||||
m := Man{
|
||||
Name: "192.168.0.1",
|
||||
}
|
||||
|
||||
m1 := Man{
|
||||
Name: "213123123",
|
||||
}
|
||||
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
valid.RegisterAliasValidation("ipe", "ip|ipv4|ipv6")
|
||||
err := valid.Struct(m)
|
||||
fmt.Println(err)
|
||||
|
||||
err1 := valid.Struct(m1)
|
||||
fmt.Println(err1)
|
||||
}
|
||||
Reference in New Issue
Block a user