feat(Go-Tool):

2020/12/31:删除源码学习部分,挪到Go-Study工程
This commit is contained in:
Huangzj
2020-12-31 16:49:40 +08:00
parent f3c056f84e
commit eb50b6273b
22 changed files with 2 additions and 5167 deletions
@@ -1,157 +0,0 @@
// 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
}
@@ -1,255 +0,0 @@
// 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)
}
}
@@ -1,150 +0,0 @@
// 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)
}
}
}
@@ -1,36 +0,0 @@
/*
* @Author : huangzj
* @Time : 2020/12/2 15:39
* @Description
*/
package testContainer
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))
}
@@ -1,62 +0,0 @@
/*
* @Author : huangzj
* @Time : 2020/12/2 11:19
* @Description
*/
package testContainer
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))
}
}
@@ -1,69 +0,0 @@
/*
* @Author : huangzj
* @Time : 2020/12/3 21:58
* @Description:测试list.go包的元素
*/
package testContainer
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))
}
@@ -1,114 +0,0 @@
/*
* @Author : huangzj
* @Time : 2020/12/7 9:33
* @Description
*/
package testContainer
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.

Before

Width:  |  Height:  |  Size: 215 KiB

@@ -1,5 +0,0 @@
我的分析文章可参考:[Go源码解析:validator.v8](https://blog.csdn.net/qq_34326321/article/details/111030128)
流程图: ![image](2、结构体缓存获取.jpg)
流程图地址:[地址](https://www.processon.com/view/link/5fd6dabb63768906e6db0d25)
@@ -1,29 +0,0 @@
# 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
@@ -1,22 +0,0 @@
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.
@@ -1,366 +0,0 @@
Package validator
================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png">[![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-8.18.2-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v8&service=github)](https://coveralls.io/github/go-playground/validator?branch=v8)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
[![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v8?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8)
![License](https://img.shields.io/dub/l/vibe-d.svg)
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.
@@ -1,285 +0,0 @@
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
}
@@ -1,852 +0,0 @@
/*
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

@@ -1,59 +0,0 @@
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)
)
@@ -1,253 +0,0 @@
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())
}
}
@@ -1,829 +0,0 @@
/**
* 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
}
}
}
@@ -1,33 +0,0 @@
/*
* @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
}
@@ -1,158 +0,0 @@
/*
* @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)
}
+2 -4
View File
@@ -62,10 +62,6 @@
2020/12/7 :更新bit位操作工作 BitTool 2020/12/7 :更新bit位操作工作 BitTool
2020/12/7 :添加container包使用示例和源码分析
2020/12/14:添加validator.v8源码解析和使用示例
2020/12/15:添加前缀树工具,实现前缀搜索和字符串搜索功能 2020/12/15:添加前缀树工具,实现前缀搜索和字符串搜索功能
2020/12/16: 添加YAML和结构体转换工具 2020/12/16: 添加YAML和结构体转换工具
@@ -79,6 +75,8 @@
2020/12/31: 修改diliver包结构 2020/12/31: 修改diliver包结构
2020/12/31:删除源码学习部分,挪到Go-Study工程
# mod vendor模式加载包 # mod vendor模式加载包
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到