add communicator api
This commit is contained in:
parent
d06ca1131e
commit
c9f514feed
|
@ -101,6 +101,10 @@
|
||||||
"Comment": "1.0-2-ga096ffc",
|
"Comment": "1.0-2-ga096ffc",
|
||||||
"Rev": "a096ffc9ada929bb7d920224cad058207d95054b"
|
"Rev": "a096ffc9ada929bb7d920224cad058207d95054b"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/zr-hebo/validator",
|
||||||
|
"Rev": "f57849af19b4b7782c8151eda271eac1b701943b"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/ssh/terminal",
|
"ImportPath": "golang.org/x/crypto/ssh/terminal",
|
||||||
"Rev": "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035"
|
"Rev": "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035"
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package capture
|
package capture
|
||||||
|
|
||||||
import (
|
import (
|
||||||
sd "github.com/zr-hebo/sniffer-agent/session-dealer"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"sync"
|
sd "github.com/zr-hebo/sniffer-agent/session-dealer"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
localIPAddr *string
|
localIPAddr *string
|
||||||
|
|
||||||
sessionPool = make(map[string]sd.ConnSession)
|
sessionPool = make(map[string]sd.ConnSession)
|
||||||
sessionPoolLock sync.Mutex
|
// sessionPoolLock sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -21,4 +22,6 @@ func init() {
|
||||||
|
|
||||||
localIPAddr = &ipAddr
|
localIPAddr = &ipAddr
|
||||||
log.Infof("parsed local ip address:%s", *localIPAddr)
|
log.Infof("parsed local ip address:%s", *localIPAddr)
|
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ import (
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/google/gopacket/pcap"
|
||||||
|
"github.com/zr-hebo/sniffer-agent/communicator"
|
||||||
"golang.org/x/net/bpf"
|
"golang.org/x/net/bpf"
|
||||||
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/gopacket/pcapgo"
|
"github.com/google/gopacket/pcapgo"
|
||||||
|
@ -114,6 +116,20 @@ func (nc *networkCard) listenNormal() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// throw packets according to a certain probability
|
||||||
|
throwPacketRate := communicator.GetConfig(communicator.THROW_PACKET_RATE).(float64)
|
||||||
|
if throwPacketRate >= 1.0 {
|
||||||
|
time.Sleep(time.Second*3)
|
||||||
|
continue
|
||||||
|
|
||||||
|
} else if 0 < throwPacketRate && throwPacketRate < 1.0 {
|
||||||
|
// fall into throw range
|
||||||
|
rn := rand.Float64()
|
||||||
|
if rn <= throwPacketRate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
|
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
|
||||||
m := packet.Metadata()
|
m := packet.Metadata()
|
||||||
m.CaptureInfo = ci
|
m.CaptureInfo = ci
|
||||||
|
|
|
@ -2,13 +2,13 @@ package communicator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
hu "github.com/zr-hebo/util-http"
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
THROW_PACKET_RATE = "throw_packet_rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -16,34 +16,15 @@ var (
|
||||||
router = mux.NewRouter()
|
router = mux.NewRouter()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configMapLock sync.RWMutex
|
||||||
|
configMap map[string]configItem
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.IntVar(&communicatePort, "communicate_port", 8088, "http server port. Default is 8088")
|
flag.IntVar(&communicatePort, "communicate_port", 8088, "http server port. Default is 8088")
|
||||||
|
|
||||||
router.Path("/get_status").Methods("GET").HandlerFunc(getStatus)
|
configMap = make(map[string]configItem)
|
||||||
router.Path("/set_config").Methods("POST").HandlerFunc(setConfig)
|
tprc := newThrowPacketRateConfig()
|
||||||
|
configMap[tprc.name] = tprc
|
||||||
}
|
}
|
||||||
|
|
||||||
func Server() {
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: "0.0.0.0:" + strconv.Itoa(communicatePort),
|
|
||||||
IdleTimeout: time.Second * 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Handle("/", router)
|
|
||||||
if err := server.ListenAndServe(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStatus(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
mp := hu.NewMouthpiece(resp)
|
|
||||||
defer mp.Convey()
|
|
||||||
mp.Data = "OK"
|
|
||||||
}
|
|
||||||
|
|
||||||
func setConfig(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
mp := hu.NewMouthpiece(resp)
|
|
||||||
defer mp.Convey()
|
|
||||||
mp.Data = "OK"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package communicator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
hu "github.com/zr-hebo/util-http"
|
||||||
|
"github.com/zr-hebo/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Server() {
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: "0.0.0.0:" + strconv.Itoa(communicatePort),
|
||||||
|
IdleTimeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Handle("/", router)
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outletCheckAlive(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
mp := hu.NewMouthpiece(resp)
|
||||||
|
defer func() {
|
||||||
|
_ = mp.Convey()
|
||||||
|
}()
|
||||||
|
|
||||||
|
mp.Data = "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
func outletGetConfig(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
mp := hu.NewMouthpiece(resp)
|
||||||
|
defer func() {
|
||||||
|
_ = mp.Convey()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ep := &struct {
|
||||||
|
ConfigName string `validate:"nonzero" json:"config_name"`
|
||||||
|
}{}
|
||||||
|
up := hu.NewUnpacker(req, ep, nil)
|
||||||
|
if err := up.Unpack(); err != nil {
|
||||||
|
mp.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validator.Validate(*ep); err != nil {
|
||||||
|
mp.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := configMap[ep.ConfigName]
|
||||||
|
if !ok {
|
||||||
|
mp.Err = fmt.Errorf("no config %s found", ep.ConfigName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mp.Data = GetConfig(ep.ConfigName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func outletSetConfig(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
mp := hu.NewMouthpiece(resp)
|
||||||
|
defer func() {
|
||||||
|
_ = mp.Convey()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ep := &struct {
|
||||||
|
ConfigName string `validate:"nonzero" json:"config_name"`
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
}{}
|
||||||
|
up := hu.NewUnpacker(req, ep, nil)
|
||||||
|
if err := up.Unpack(); err != nil {
|
||||||
|
mp.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := validator.Validate(*ep); err != nil {
|
||||||
|
mp.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mp.Err = SetConfig(ep.ConfigName, ep.Value)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package communicator
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// SetConfig set config by config key(name) and value
|
||||||
|
func SetConfig(key string, val interface{}) (err error) {
|
||||||
|
configMapLock.Lock()
|
||||||
|
defer configMapLock.Unlock()
|
||||||
|
|
||||||
|
config, ok := configMap[key]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("no config %s exist", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.setVal(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig get config value by config key(name)
|
||||||
|
func GetConfig(key string) (val interface{}) {
|
||||||
|
configMapLock.RLock()
|
||||||
|
defer configMapLock.RUnlock()
|
||||||
|
|
||||||
|
config := configMap[key]
|
||||||
|
return config.getVal()
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package communicator
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type configItem interface {
|
||||||
|
setVal (interface{}) error
|
||||||
|
getVal () interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type throwPacketRateConfig struct {
|
||||||
|
name string
|
||||||
|
value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThrowPacketRateConfig() (tpr *throwPacketRateConfig) {
|
||||||
|
tpr = &throwPacketRateConfig{
|
||||||
|
name: THROW_PACKET_RATE,
|
||||||
|
value: 0.0,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *throwPacketRateConfig) setVal (val interface{}) (err error){
|
||||||
|
realVal, ok := val.(float64)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("cannot reansform val: %v to float64", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.value = realVal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *throwPacketRateConfig) getVal () (val interface{}){
|
||||||
|
return tc.value
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package communicator
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.Path("/check_alive").Methods("GET").HandlerFunc(outletCheckAlive)
|
||||||
|
router.Path("/get_config").Methods("GET").HandlerFunc(outletGetConfig)
|
||||||
|
router.Path("/set_config").Methods("POST").HandlerFunc(outletSetConfig)
|
||||||
|
}
|
|
@ -14,11 +14,11 @@ function execute_real(){
|
||||||
sleep 1
|
sleep 1
|
||||||
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e ""
|
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e ""
|
||||||
sleep 1
|
sleep 1
|
||||||
insert_cmd="insert into unibase.haha(id, name) values(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
insert_cmd="insert into unibase.haha(id, name) values(10, 'aaaa')"
|
||||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||||
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e "$insert_cmd"
|
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e "$insert_cmd"
|
||||||
sleep 1
|
sleep 1
|
||||||
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e "use unibase; select * from haha; drop table haha"
|
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e "use unibase; select * from haha; drop table haha"
|
||||||
|
|
|
@ -41,7 +41,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ComAuth = 141
|
maxSQLLen = 5*1024*1024
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client information.
|
// Client information.
|
||||||
|
|
|
@ -24,6 +24,7 @@ type MysqlSession struct {
|
||||||
packageOffset int64
|
packageOffset int64
|
||||||
expectReceiveSize int
|
expectReceiveSize int
|
||||||
coverRanges []*jigsaw
|
coverRanges []*jigsaw
|
||||||
|
coverRange *jigsaw
|
||||||
expectSendSize int
|
expectSendSize int
|
||||||
prepareInfo *prepareInfo
|
prepareInfo *prepareInfo
|
||||||
cachedPrepareStmt map[int][]byte
|
cachedPrepareStmt map[int][]byte
|
||||||
|
@ -137,6 +138,7 @@ func mergeRanges(currRange *jigsaw, pkgRanges []*jigsaw) (mergedRange *jigsaw, n
|
||||||
return currRange, newPkgRanges
|
return currRange, newPkgRanges
|
||||||
|
|
||||||
} else if len(pkgRanges) == 1 {
|
} else if len(pkgRanges) == 1 {
|
||||||
|
//
|
||||||
nextRange = pkgRanges[0]
|
nextRange = pkgRanges[0]
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -253,6 +255,7 @@ func (ms *MysqlSession) GenerateQueryPiece() (qp model.QueryPiece) {
|
||||||
ms.expectSendSize = 0
|
ms.expectSendSize = 0
|
||||||
ms.prepareInfo = nil
|
ms.prepareInfo = nil
|
||||||
ms.coverRanges = make([]*jigsaw, 0, 4)
|
ms.coverRanges = make([]*jigsaw, 0, 4)
|
||||||
|
ms.coverRange = nil
|
||||||
ms.lastSeq = -1
|
ms.lastSeq = -1
|
||||||
ms.ignoreAckID = -1
|
ms.ignoreAckID = -1
|
||||||
ms.sendSize = 0
|
ms.sendSize = 0
|
||||||
|
@ -267,6 +270,11 @@ func (ms *MysqlSession) GenerateQueryPiece() (qp model.QueryPiece) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ms.cachedStmtBytes) > maxSQLLen {
|
||||||
|
log.Warn("sql in cache is too long, ignore it")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var mqp *model.PooledMysqlQueryPiece
|
var mqp *model.PooledMysqlQueryPiece
|
||||||
var querySQLInBytes []byte
|
var querySQLInBytes []byte
|
||||||
if ms.cachedStmtBytes[0] > 32 {
|
if ms.cachedStmtBytes[0] > 32 {
|
||||||
|
@ -327,7 +335,6 @@ func (ms *MysqlSession) GenerateQueryPiece() (qp model.QueryPiece) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if strictMode && mqp != nil && mqp.VisitUser == nil {
|
if strictMode && mqp != nil && mqp.VisitUser == nil {
|
||||||
user, db, err := querySessionInfo(ms.serverPort, mqp.SessionID)
|
user, db, err := querySessionInfo(ms.serverPort, mqp.SessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,10 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
go_import_path: gopkg.in/validator.v2
|
||||||
|
script:
|
||||||
|
- go test -race -v -bench=.
|
||||||
|
notifications:
|
||||||
|
email: false
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,167 @@
|
||||||
|
Package validator
|
||||||
|
================
|
||||||
|
|
||||||
|
Package validator implements variable validations
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
Just use go get.
|
||||||
|
|
||||||
|
go get gopkg.in/validator.v2
|
||||||
|
|
||||||
|
And then just import the package into your own code.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/validator.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
Please see http://godoc.org/gopkg.in/validator.v2 for detailed usage docs.
|
||||||
|
A simple example would be.
|
||||||
|
|
||||||
|
type NewUserRequest struct {
|
||||||
|
Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]*$"`
|
||||||
|
Name string `validate:"nonzero"`
|
||||||
|
Age int `validate:"min=21"`
|
||||||
|
Password string `validate:"min=8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
nur := NewUserRequest{Username: "something", Age: 20}
|
||||||
|
if errs := validator.Validate(nur); errs != nil {
|
||||||
|
// values not valid, deal with errors here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Builtin validators
|
||||||
|
|
||||||
|
Here is the list of validators buildin in the package.
|
||||||
|
|
||||||
|
len
|
||||||
|
For numeric numbers, max will simply make sure 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)
|
||||||
|
|
||||||
|
max
|
||||||
|
For numeric numbers, max will simply make sure that the
|
||||||
|
value is lesser 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)
|
||||||
|
|
||||||
|
min
|
||||||
|
For numeric numbers, min will simply make sure 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)
|
||||||
|
|
||||||
|
nonzero
|
||||||
|
This validates that the value is not zero. The appropriate
|
||||||
|
zero value is given by the Go spec (e.g. for int it's 0, for
|
||||||
|
string it's "", for pointers is nil, etc.) For structs, it
|
||||||
|
will not check to see if the struct itself has all zero
|
||||||
|
values, instead use a pointer or put nonzero on the struct's
|
||||||
|
keys that you care about. (Usage: nonzero)
|
||||||
|
|
||||||
|
regexp
|
||||||
|
Only valid for string types, it will validator that the
|
||||||
|
value matches the regular expression provided as parameter.
|
||||||
|
(Usage: regexp=^a.*b$)
|
||||||
|
|
||||||
|
Custom validators
|
||||||
|
|
||||||
|
It is possible to define custom validators by using SetValidationFunc.
|
||||||
|
First, one needs to create a validation function.
|
||||||
|
|
||||||
|
// Very simple validator
|
||||||
|
func notZZ(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
if st.Kind() != reflect.String {
|
||||||
|
return errors.New("notZZ only validates strings")
|
||||||
|
}
|
||||||
|
if st.String() == "ZZ" {
|
||||||
|
return errors.New("value cannot be ZZ")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Then one needs to add it to the list of validators and give it a "tag"
|
||||||
|
name.
|
||||||
|
|
||||||
|
validator.SetValidationFunc("notzz", notZZ)
|
||||||
|
|
||||||
|
Then it is possible to use the notzz validation tag. This will print
|
||||||
|
"Field A error: value cannot be ZZ"
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
A string `validate:"nonzero,notzz"`
|
||||||
|
}
|
||||||
|
t := T{"ZZ"}
|
||||||
|
if errs := validator.Validate(t); errs != nil {
|
||||||
|
fmt.Printf("Field A error: %s\n", errs["A"][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also have multiple sets of validator rules with SetTag().
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
A int `foo:"nonzero" bar:"min=10"`
|
||||||
|
}
|
||||||
|
t := T{5}
|
||||||
|
SetTag("foo")
|
||||||
|
validator.Validate(t) // valid as it's nonzero
|
||||||
|
SetTag("bar")
|
||||||
|
validator.Validate(t) // invalid as it's less than 10
|
||||||
|
|
||||||
|
SetTag is probably better used with multiple validators.
|
||||||
|
|
||||||
|
fooValidator := validator.NewValidator()
|
||||||
|
fooValidator.SetTag("foo")
|
||||||
|
barValidator := validator.NewValidator()
|
||||||
|
barValidator.SetTag("bar")
|
||||||
|
fooValidator.Validate(t)
|
||||||
|
barValidator.Validate(t)
|
||||||
|
|
||||||
|
This keeps the default validator's tag clean. Again, please refer to
|
||||||
|
godocs for a lot of more examples and different uses.
|
||||||
|
|
||||||
|
Pull requests policy
|
||||||
|
====================
|
||||||
|
|
||||||
|
tl;dr. Contributions are welcome.
|
||||||
|
|
||||||
|
The repository is organized in version branches. Pull requests to, say, the
|
||||||
|
`v2` branch that break API compatibility will not be accepted. It is okay to
|
||||||
|
break the API in master, *not in the branches*.
|
||||||
|
|
||||||
|
As for validation functions, the preference is to keep the main code simple
|
||||||
|
and add most new functions to the validator-contrib repository.
|
||||||
|
|
||||||
|
https://github.com/go-validator/validator-contrib
|
||||||
|
|
||||||
|
For improvements and/or fixes to the builtin validation functions, please
|
||||||
|
make sure the behaviour will not break existing functionality in the branches.
|
||||||
|
If you see a case where the functionality of the builtin will change
|
||||||
|
significantly, please send a pull request against `master`. We can discuss then
|
||||||
|
whether the changes should be incorporated in the version branches as well.
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
Copyright 2014 Roberto Teixeira <robteix@robteix.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Package validator implements value validations
|
||||||
|
//
|
||||||
|
// Copyright 2014 Roberto Teixeira <robteix@robteix.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nonzero tests whether a variable value non-zero
|
||||||
|
// as defined by the golang spec.
|
||||||
|
func nonzero(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
valid := true
|
||||||
|
switch st.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
valid = len(st.String()) != 0
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
valid = !st.IsNil()
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
valid = st.Len() != 0
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
valid = st.Int() != 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
valid = st.Uint() != 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
valid = st.Float() != 0
|
||||||
|
case reflect.Bool:
|
||||||
|
valid = st.Bool()
|
||||||
|
case reflect.Invalid:
|
||||||
|
valid = false // always invalid
|
||||||
|
case reflect.Struct:
|
||||||
|
valid = true // always valid since only nil pointers are empty
|
||||||
|
default:
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
return ErrZeroValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// length tests whether a variable's length is equal to a given
|
||||||
|
// value. For strings it tests the number of characters whereas
|
||||||
|
// for maps and slices it tests the number of items.
|
||||||
|
func length(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
valid := true
|
||||||
|
switch st.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
valid = int64(len(st.String())) == p
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
valid = int64(st.Len()) == p
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
valid = st.Int() == p
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
p, err := asUint(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
valid = st.Uint() == p
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
p, err := asFloat(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
valid = st.Float() == p
|
||||||
|
default:
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return ErrLen
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// min tests whether a variable value is larger or equal to a given
|
||||||
|
// number. For number types, it's a simple lesser-than test; for
|
||||||
|
// strings it tests the number of characters whereas for maps
|
||||||
|
// and slices it tests the number of items.
|
||||||
|
func min(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
invalid := false
|
||||||
|
switch st.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = int64(len(st.String())) < p
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = int64(st.Len()) < p
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = st.Int() < p
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
p, err := asUint(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = st.Uint() < p
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
p, err := asFloat(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = st.Float() < p
|
||||||
|
default:
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
||||||
|
if invalid {
|
||||||
|
return ErrMin
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// max tests whether a variable value is lesser than a given
|
||||||
|
// value. For numbers, it's a simple lesser-than test; for
|
||||||
|
// strings it tests the number of characters whereas for maps
|
||||||
|
// and slices it tests the number of items.
|
||||||
|
func max(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
var invalid bool
|
||||||
|
switch st.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = int64(len(st.String())) > p
|
||||||
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = int64(st.Len()) > p
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
p, err := asInt(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = st.Int() > p
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
p, err := asUint(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = st.Uint() > p
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
p, err := asFloat(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
invalid = st.Float() > p
|
||||||
|
default:
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
||||||
|
if invalid {
|
||||||
|
return ErrMax
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// regex is the builtin validation function that checks
|
||||||
|
// whether the string variable matches a regular expression
|
||||||
|
func regex(v interface{}, param string) error {
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
re, err := regexp.Compile(param)
|
||||||
|
if err != nil {
|
||||||
|
return ErrBadParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
if !re.MatchString(s) {
|
||||||
|
return ErrRegexp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asInt retuns the parameter as a int64
|
||||||
|
// or panics if it can't convert
|
||||||
|
func asInt(param string) (int64, error) {
|
||||||
|
i, err := strconv.ParseInt(param, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrBadParameter
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asUint retuns the parameter as a uint64
|
||||||
|
// or panics if it can't convert
|
||||||
|
func asUint(param string) (uint64, error) {
|
||||||
|
i, err := strconv.ParseUint(param, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrBadParameter
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asFloat retuns the parameter as a float64
|
||||||
|
// or panics if it can't convert
|
||||||
|
func asFloat(param string) (float64, error) {
|
||||||
|
i, err := strconv.ParseFloat(param, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0.0, ErrBadParameter
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
// Package validator implements value validations
|
||||||
|
//
|
||||||
|
// Copyright 2014 Roberto Teixeira <robteix@robteix.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package validator implements value validations based on struct tags.
|
||||||
|
|
||||||
|
In code it is often necessary to validate that a given value is valid before
|
||||||
|
using it for something. A typical example might be something like this.
|
||||||
|
|
||||||
|
if age < 18 {
|
||||||
|
return error.New("age cannot be under 18")
|
||||||
|
}
|
||||||
|
|
||||||
|
This is a simple enough example, but it can get significantly more complex,
|
||||||
|
especially when dealing with structs.
|
||||||
|
|
||||||
|
l := len(strings.Trim(s.Username))
|
||||||
|
if l < 3 || l > 40 || !regexp.MatchString("^[a-zA-Z]$", s.Username) || s.Age < 18 || s.Password {
|
||||||
|
return errors.New("Invalid request")
|
||||||
|
}
|
||||||
|
|
||||||
|
You get the idea. Package validator allows one to define valid values as
|
||||||
|
struct tags when defining a new struct type.
|
||||||
|
|
||||||
|
type NewUserRequest struct {
|
||||||
|
Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]*$"`
|
||||||
|
Name string `validate:"nonzero"`
|
||||||
|
Age int `validate:"min=18"`
|
||||||
|
Password string `validate:"min=8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Then validating a variable of type NewUserRequest becomes trivial.
|
||||||
|
|
||||||
|
nur := NewUserRequest{Username: "something", ...}
|
||||||
|
if errs := validator.Validate(nur); errs != nil {
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
|
||||||
|
Builtin validator functions
|
||||||
|
|
||||||
|
Here is the list of validator functions builtin in the package.
|
||||||
|
|
||||||
|
len
|
||||||
|
For numeric numbers, len will simply make sure 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)
|
||||||
|
|
||||||
|
max
|
||||||
|
For numeric numbers, max will simply make sure that the value is
|
||||||
|
lesser 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)
|
||||||
|
|
||||||
|
min
|
||||||
|
For numeric numbers, min will simply make sure 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)
|
||||||
|
|
||||||
|
nonzero
|
||||||
|
This validates that the value is not zero. The appropriate zero value
|
||||||
|
is given by the Go spec (e.g. for int it's 0, for string it's "", for
|
||||||
|
pointers is nil, etc.) Usage: nonzero
|
||||||
|
|
||||||
|
regexp
|
||||||
|
Only valid for string types, it will validate that the value matches
|
||||||
|
the regular expression provided as parameter. (Usage: regexp=^a.*b$)
|
||||||
|
|
||||||
|
|
||||||
|
Note that there are no tests to prevent conflicting validator parameters. For
|
||||||
|
instance, these fields will never be valid.
|
||||||
|
|
||||||
|
...
|
||||||
|
A int `validate:"max=0,min=1"`
|
||||||
|
B string `validate:"len=10,regexp=^$"
|
||||||
|
...
|
||||||
|
|
||||||
|
Custom validation functions
|
||||||
|
|
||||||
|
It is possible to define custom validation functions by using SetValidationFunc.
|
||||||
|
First, one needs to create a validation function.
|
||||||
|
|
||||||
|
// Very simple validation func
|
||||||
|
func notZZ(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
if st.Kind() != reflect.String {
|
||||||
|
return validate.ErrUnsupported
|
||||||
|
}
|
||||||
|
if st.String() == "ZZ" {
|
||||||
|
return errors.New("value cannot be ZZ")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Then one needs to add it to the list of validation funcs and give it a "tag" name.
|
||||||
|
|
||||||
|
validate.SetValidationFunc("notzz", notZZ)
|
||||||
|
|
||||||
|
Then it is possible to use the notzz validation tag. This will print
|
||||||
|
"Field A error: value cannot be ZZ"
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
A string `validate:"nonzero,notzz"`
|
||||||
|
}
|
||||||
|
t := T{"ZZ"}
|
||||||
|
if errs := validator.Validate(t); errs != nil {
|
||||||
|
fmt.Printf("Field A error: %s\n", errs["A"][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
To use parameters, it is very similar.
|
||||||
|
|
||||||
|
// Very simple validator with parameter
|
||||||
|
func notSomething(v interface{}, param string) error {
|
||||||
|
st := reflect.ValueOf(v)
|
||||||
|
if st.Kind() != reflect.String {
|
||||||
|
return validate.ErrUnsupported
|
||||||
|
}
|
||||||
|
if st.String() == param {
|
||||||
|
return errors.New("value cannot be " + param)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
And then the code below should print "Field A error: value cannot be ABC".
|
||||||
|
|
||||||
|
validator.SetValidationFunc("notsomething", notSomething)
|
||||||
|
type T struct {
|
||||||
|
A string `validate:"notsomething=ABC"`
|
||||||
|
}
|
||||||
|
t := T{"ABC"}
|
||||||
|
if errs := validator.Validate(t); errs != nil {
|
||||||
|
fmt.Printf("Field A error: %s\n", errs["A"][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
As well, it is possible to overwrite builtin validation functions.
|
||||||
|
|
||||||
|
validate.SetValidationFunc("min", myMinFunc)
|
||||||
|
|
||||||
|
And you can delete a validation function by setting it to nil.
|
||||||
|
|
||||||
|
validate.SetValidationFunc("notzz", nil)
|
||||||
|
validate.SetValidationFunc("nonzero", nil)
|
||||||
|
|
||||||
|
Using a non-existing validation func in a field tag will always return
|
||||||
|
false and with error validate.ErrUnknownTag.
|
||||||
|
|
||||||
|
Finally, package validator also provides a helper function that can be used
|
||||||
|
to validate simple variables/values.
|
||||||
|
|
||||||
|
// errs: nil
|
||||||
|
errs = validator.Valid(42, "min=10, max=50")
|
||||||
|
|
||||||
|
// errs: [validate.ErrZeroValue]
|
||||||
|
errs = validator.Valid(nil, "nonzero")
|
||||||
|
|
||||||
|
// errs: [validate.ErrMin,validate.ErrMax]
|
||||||
|
errs = validator.Valid("hi", "nonzero,min=3,max=2")
|
||||||
|
|
||||||
|
Custom tag name
|
||||||
|
|
||||||
|
In case there is a reason why one would not wish to use tag 'validate' (maybe due to
|
||||||
|
a conflict with a different package), it is possible to tell the package to use
|
||||||
|
a different tag.
|
||||||
|
|
||||||
|
validator.SetTag("valid")
|
||||||
|
|
||||||
|
Then.
|
||||||
|
|
||||||
|
Type T struct {
|
||||||
|
A int `valid:"min=8, max=10"`
|
||||||
|
B string `valid:"nonzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTag is permanent. The new tag name will be used until it is again changed
|
||||||
|
with a new call to SetTag. A way to temporarily use a different tag exists.
|
||||||
|
|
||||||
|
validator.WithTag("foo").Validate(t)
|
||||||
|
validator.WithTag("bar").Validate(t)
|
||||||
|
// But this will go back to using 'validate'
|
||||||
|
validator.Validate(t)
|
||||||
|
|
||||||
|
Multiple validators
|
||||||
|
|
||||||
|
You may often need to have a different set of validation
|
||||||
|
rules for different situations. In all the examples above,
|
||||||
|
we only used the default validator but you could create a
|
||||||
|
new one and set specific rules for it.
|
||||||
|
|
||||||
|
For instance, you might use the same struct to decode incoming JSON for a REST API
|
||||||
|
but your needs will change when you're using it to, say, create a new instance
|
||||||
|
in storage vs. when you need to change something.
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string `validate:"nonzero"`
|
||||||
|
Name string `validate:"nonzero"`
|
||||||
|
Age int `validate:"nonzero"`
|
||||||
|
Password string `validate:"nonzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Maybe when creating a new user, you need to make sure all values in the struct are filled,
|
||||||
|
but then you use the same struct to handle incoming requests to, say, change the password,
|
||||||
|
in which case you only need the Username and the Password and don't care for the others.
|
||||||
|
You might use two different validators.
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string `creating:"nonzero" chgpw:"nonzero"`
|
||||||
|
Name string `creating:"nonzero"`
|
||||||
|
Age int `creating:"nonzero"`
|
||||||
|
Password string `creating:"nonzero" chgpw:"nonzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
creationValidator = validator.NewValidator()
|
||||||
|
chgPwValidator = validator.NewValidator()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
creationValidator.SetTag("creating")
|
||||||
|
chgPwValidator.SetTag("chgpw")
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var u User
|
||||||
|
json.NewDecoder(r.Body).Decode(&user)
|
||||||
|
if errs := creationValidator.Validate(user); errs != nil {
|
||||||
|
// the request did not include all of the User
|
||||||
|
// struct fields, so send a http.StatusBadRequest
|
||||||
|
// back or something
|
||||||
|
}
|
||||||
|
// create the new user
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetNewUserPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var u User
|
||||||
|
json.NewDecoder(r.Body).Decode(&user)
|
||||||
|
if errs := chgPwValidator.Validate(user); errs != nil {
|
||||||
|
// the request did not Username and Password,
|
||||||
|
// so send a http.StatusBadRequest
|
||||||
|
// back or something
|
||||||
|
}
|
||||||
|
// save the new password
|
||||||
|
}
|
||||||
|
|
||||||
|
It is also possible to do all of that using only the default validator as long
|
||||||
|
as SetTag is always called before calling validator.Validate() or you chain the
|
||||||
|
with WithTag().
|
||||||
|
|
||||||
|
*/
|
||||||
|
package validator
|
|
@ -0,0 +1,372 @@
|
||||||
|
// Package validator implements value validations
|
||||||
|
//
|
||||||
|
// Copyright 2014 Roberto Teixeira <robteix@robteix.com>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TextErr is an error that also implements the TextMarshaller interface for
|
||||||
|
// serializing out to various plain text encodings. Packages creating their
|
||||||
|
// own custom errors should use TextErr if they're intending to use serializing
|
||||||
|
// formats like json, msgpack etc.
|
||||||
|
type TextErr struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (t TextErr) Error() string {
|
||||||
|
return t.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements the TextMarshaller
|
||||||
|
func (t TextErr) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(t.Err.Error()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrZeroValue is the error returned when variable has zero valud
|
||||||
|
// and nonzero was specified
|
||||||
|
ErrZeroValue = TextErr{errors.New("不能为空!")}
|
||||||
|
// ErrMin is the error returned when variable is less than mininum
|
||||||
|
// value specified
|
||||||
|
ErrMin = TextErr{errors.New("不能小于最小值")}
|
||||||
|
// ErrMax is the error returned when variable is more than
|
||||||
|
// maximum specified
|
||||||
|
ErrMax = TextErr{errors.New("不能大于最大值")}
|
||||||
|
// ErrLen is the error returned when length is not equal to
|
||||||
|
// param specified
|
||||||
|
ErrLen = TextErr{errors.New("无效的长度")}
|
||||||
|
// ErrRegexp is the error returned when the value does not
|
||||||
|
// match the provided regular expression parameter
|
||||||
|
ErrRegexp = TextErr{errors.New("不能匹配对应的正则")}
|
||||||
|
// ErrUnsupported is the error error returned when a validation rule
|
||||||
|
// is used with an unsupported variable type
|
||||||
|
ErrUnsupported = TextErr{errors.New("不支持的验证类型")}
|
||||||
|
// ErrBadParameter is the error returned when an invalid parameter
|
||||||
|
// is provided to a validation rule (e.g. a string where an int was
|
||||||
|
// expected (max=foo,len=bar) or missing a parameter when one is required (len=))
|
||||||
|
ErrBadParameter = TextErr{errors.New("无效的参数")}
|
||||||
|
// ErrUnknownTag is the error returned when an unknown tag is found
|
||||||
|
ErrUnknownTag = TextErr{errors.New("unknown tag")}
|
||||||
|
// ErrInvalid is the error returned when variable is invalid
|
||||||
|
// (normally a nil pointer)
|
||||||
|
ErrInvalid = TextErr{errors.New("invalid value")}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorMap is a map which contains all errors from validating a struct.
|
||||||
|
type ErrorMap map[string]ErrorArray
|
||||||
|
|
||||||
|
// ErrorMap implements the Error interface so we can check error against nil.
|
||||||
|
// The returned error is if existent the first error which was added to the map.
|
||||||
|
func (err ErrorMap) Error() string {
|
||||||
|
for k, errs := range err {
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Sprintf("验证 %s 失败: %s", k, errs.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorArray is a slice of errors returned by the Validate function.
|
||||||
|
type ErrorArray []error
|
||||||
|
|
||||||
|
// ErrorArray implements the Error interface and returns the first error as
|
||||||
|
// string if existent.
|
||||||
|
func (err ErrorArray) Error() string {
|
||||||
|
if len(err) > 0 {
|
||||||
|
return err[0].Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidationFunc is a function that receives the value of a
|
||||||
|
// field and a parameter used for the respective validation tag.
|
||||||
|
type ValidationFunc func(v interface{}, param string) error
|
||||||
|
|
||||||
|
// Validator implements a validator
|
||||||
|
type Validator struct {
|
||||||
|
// Tag name being used.
|
||||||
|
tagName string
|
||||||
|
// validationFuncs is a map of ValidationFuncs indexed
|
||||||
|
// by their name.
|
||||||
|
validationFuncs map[string]ValidationFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper validator so users can use the
|
||||||
|
// functions directly from the package
|
||||||
|
var defaultValidator = NewValidator()
|
||||||
|
|
||||||
|
// NewValidator creates a new Validator
|
||||||
|
func NewValidator() *Validator {
|
||||||
|
return &Validator{
|
||||||
|
tagName: "validate",
|
||||||
|
validationFuncs: map[string]ValidationFunc{
|
||||||
|
"nonzero": nonzero,
|
||||||
|
"len": length,
|
||||||
|
"min": min,
|
||||||
|
"max": max,
|
||||||
|
"regexp": regex,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTag allows you to change the tag name used in structs
|
||||||
|
func SetTag(tag string) {
|
||||||
|
defaultValidator.SetTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTag allows you to change the tag name used in structs
|
||||||
|
func (mv *Validator) SetTag(tag string) {
|
||||||
|
mv.tagName = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTag creates a new Validator with the new tag name. It is
|
||||||
|
// useful to chain-call with Validate so we don't change the tag
|
||||||
|
// name permanently: validator.WithTag("foo").Validate(t)
|
||||||
|
func WithTag(tag string) *Validator {
|
||||||
|
return defaultValidator.WithTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTag creates a new Validator with the new tag name. It is
|
||||||
|
// useful to chain-call with Validate so we don't change the tag
|
||||||
|
// name permanently: validator.WithTag("foo").Validate(t)
|
||||||
|
func (mv *Validator) WithTag(tag string) *Validator {
|
||||||
|
v := mv.copy()
|
||||||
|
v.SetTag(tag)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy a validator
|
||||||
|
func (mv *Validator) copy() *Validator {
|
||||||
|
newFuncs := map[string]ValidationFunc{}
|
||||||
|
for k, f := range mv.validationFuncs {
|
||||||
|
newFuncs[k] = f
|
||||||
|
}
|
||||||
|
return &Validator{
|
||||||
|
tagName: mv.tagName,
|
||||||
|
validationFuncs: newFuncs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValidationFunc sets the function to be used for a given
|
||||||
|
// validation constraint. Calling this function with nil vf
|
||||||
|
// is the same as removing the constraint function from the list.
|
||||||
|
func SetValidationFunc(name string, vf ValidationFunc) error {
|
||||||
|
return defaultValidator.SetValidationFunc(name, vf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValidationFunc sets the function to be used for a given
|
||||||
|
// validation constraint. Calling this function with nil vf
|
||||||
|
// is the same as removing the constraint function from the list.
|
||||||
|
func (mv *Validator) SetValidationFunc(name string, vf ValidationFunc) error {
|
||||||
|
if name == "" {
|
||||||
|
return errors.New("name cannot be empty")
|
||||||
|
}
|
||||||
|
if vf == nil {
|
||||||
|
delete(mv.validationFuncs, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mv.validationFuncs[name] = vf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the fields of a struct based
|
||||||
|
// on 'validator' tags and returns errors found indexed
|
||||||
|
// by the field name.
|
||||||
|
func Validate(v interface{}) error {
|
||||||
|
return defaultValidator.Validate(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the fields of a struct based
|
||||||
|
// on 'validator' tags and returns errors found indexed
|
||||||
|
// by the field name.
|
||||||
|
func (mv *Validator) Validate(v interface{}) error {
|
||||||
|
sv := reflect.ValueOf(v)
|
||||||
|
st := reflect.TypeOf(v)
|
||||||
|
if sv.Kind() == reflect.Ptr && !sv.IsNil() {
|
||||||
|
return mv.Validate(sv.Elem().Interface())
|
||||||
|
}
|
||||||
|
if sv.Kind() != reflect.Struct && sv.Kind() != reflect.Interface {
|
||||||
|
return ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
nfields := sv.NumField()
|
||||||
|
m := make(ErrorMap)
|
||||||
|
for i := 0; i < nfields; i++ {
|
||||||
|
fname := st.Field(i).Name
|
||||||
|
if !unicode.IsUpper(rune(fname[0])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f := sv.Field(i)
|
||||||
|
// deal with pointers
|
||||||
|
for f.Kind() == reflect.Ptr && !f.IsNil() {
|
||||||
|
f = f.Elem()
|
||||||
|
}
|
||||||
|
tag := st.Field(i).Tag.Get(mv.tagName)
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var errs ErrorArray
|
||||||
|
|
||||||
|
if tag != "" {
|
||||||
|
err := mv.Valid(f.Interface(), tag)
|
||||||
|
if errors, ok := err.(ErrorArray); ok {
|
||||||
|
errs = errors
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
errs = ErrorArray{err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.deepValidateCollection(f, fname, m) // no-op if field is not a struct, interface, array, slice or map
|
||||||
|
|
||||||
|
showFieldName := st.Field(i).Tag.Get("json")
|
||||||
|
if len(showFieldName) < 1 {
|
||||||
|
showFieldName = st.Field(i).Name
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
m[showFieldName] = errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) > 0 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mv *Validator) deepValidateCollection(f reflect.Value, fname string, m ErrorMap) {
|
||||||
|
switch f.Kind() {
|
||||||
|
case reflect.Struct, reflect.Interface, reflect.Ptr:
|
||||||
|
e := mv.Validate(f.Interface())
|
||||||
|
if e, ok := e.(ErrorMap); ok && len(e) > 0 {
|
||||||
|
for j, k := range e {
|
||||||
|
m[fname+"."+j] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
for i := 0; i < f.Len(); i++ {
|
||||||
|
mv.deepValidateCollection(f.Index(i), fmt.Sprintf("%s[%d]", fname, i), m)
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range f.MapKeys() {
|
||||||
|
mv.deepValidateCollection(key, fmt.Sprintf("%s[%+v](key)", fname, key.Interface()), m) // validate the map key
|
||||||
|
value := f.MapIndex(key)
|
||||||
|
mv.deepValidateCollection(value, fmt.Sprintf("%s[%+v](value)", fname, key.Interface()), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid validates a value based on the provided
|
||||||
|
// tags and returns errors found or nil.
|
||||||
|
func Valid(val interface{}, tags string) error {
|
||||||
|
return defaultValidator.Valid(val, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid validates a value based on the provided
|
||||||
|
// tags and returns errors found or nil.
|
||||||
|
func (mv *Validator) Valid(val interface{}, tags string) error {
|
||||||
|
if tags == "-" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(val)
|
||||||
|
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||||
|
return mv.Valid(v.Elem().Interface(), tags)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
err = mv.validateVar(nil, tags)
|
||||||
|
default:
|
||||||
|
err = mv.validateVar(val, tags)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateVar validates one single variable
|
||||||
|
func (mv *Validator) validateVar(v interface{}, tag string) error {
|
||||||
|
tags, err := mv.parseTags(tag)
|
||||||
|
if err != nil {
|
||||||
|
// unknown tag found, give up.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
errs := make(ErrorArray, 0, len(tags))
|
||||||
|
for _, t := range tags {
|
||||||
|
if err := t.Fn(v, t.Param); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag represents one of the tag items
|
||||||
|
type tag struct {
|
||||||
|
Name string // name of the tag
|
||||||
|
Fn ValidationFunc // validation function to call
|
||||||
|
Param string // parameter to send to the validation function
|
||||||
|
}
|
||||||
|
|
||||||
|
// separate by no escaped commas
|
||||||
|
var sepPattern = regexp.MustCompile(`((?:^|[^\\])(?:\\\\)*),`)
|
||||||
|
|
||||||
|
func splitUnescapedComma(str string) []string {
|
||||||
|
ret := []string{}
|
||||||
|
indexes := sepPattern.FindAllStringIndex(str, -1)
|
||||||
|
last := 0
|
||||||
|
for _, is := range indexes {
|
||||||
|
ret = append(ret, str[last:is[1]-1])
|
||||||
|
last = is[1]
|
||||||
|
}
|
||||||
|
ret = append(ret, str[last:])
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTags parses all individual tags found within a struct tag.
|
||||||
|
func (mv *Validator) parseTags(t string) ([]tag, error) {
|
||||||
|
tl := splitUnescapedComma(t)
|
||||||
|
tags := make([]tag, 0, len(tl))
|
||||||
|
for _, i := range tl {
|
||||||
|
i = strings.Replace(i, `\,`, ",", -1)
|
||||||
|
tg := tag{}
|
||||||
|
v := strings.SplitN(i, "=", 2)
|
||||||
|
tg.Name = strings.Trim(v[0], " ")
|
||||||
|
if tg.Name == "" {
|
||||||
|
return []tag{}, ErrUnknownTag
|
||||||
|
}
|
||||||
|
if len(v) > 1 {
|
||||||
|
tg.Param = strings.Trim(v[1], " ")
|
||||||
|
}
|
||||||
|
var found bool
|
||||||
|
if tg.Fn, found = mv.validationFuncs[tg.Name]; !found {
|
||||||
|
return []tag{}, ErrUnknownTag
|
||||||
|
}
|
||||||
|
tags = append(tags, tg)
|
||||||
|
|
||||||
|
}
|
||||||
|
return tags, nil
|
||||||
|
}
|
Loading…
Reference in New Issue