add communicator api
This commit is contained in:
parent
d06ca1131e
commit
c9f514feed
|
@ -101,6 +101,10 @@
|
|||
"Comment": "1.0-2-ga096ffc",
|
||||
"Rev": "a096ffc9ada929bb7d920224cad058207d95054b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/zr-hebo/validator",
|
||||
"Rev": "f57849af19b4b7782c8151eda271eac1b701943b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh/terminal",
|
||||
"Rev": "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035"
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package capture
|
||||
|
||||
import (
|
||||
sd "github.com/zr-hebo/sniffer-agent/session-dealer"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
sd "github.com/zr-hebo/sniffer-agent/session-dealer"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
localIPAddr *string
|
||||
|
||||
sessionPool = make(map[string]sd.ConnSession)
|
||||
sessionPoolLock sync.Mutex
|
||||
// sessionPoolLock sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -21,4 +22,6 @@ func init() {
|
|||
|
||||
localIPAddr = &ipAddr
|
||||
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/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
"github.com/zr-hebo/sniffer-agent/communicator"
|
||||
"golang.org/x/net/bpf"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket/pcapgo"
|
||||
|
@ -114,6 +116,20 @@ func (nc *networkCard) listenNormal() {
|
|||
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)
|
||||
m := packet.Metadata()
|
||||
m.CaptureInfo = ci
|
||||
|
|
|
@ -2,13 +2,13 @@ package communicator
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
hu "github.com/zr-hebo/util-http"
|
||||
_ "net/http/pprof"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
THROW_PACKET_RATE = "throw_packet_rate"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -16,34 +16,15 @@ var (
|
|||
router = mux.NewRouter()
|
||||
)
|
||||
|
||||
var (
|
||||
configMapLock sync.RWMutex
|
||||
configMap map[string]configItem
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&communicatePort, "communicate_port", 8088, "http server port. Default is 8088")
|
||||
|
||||
router.Path("/get_status").Methods("GET").HandlerFunc(getStatus)
|
||||
router.Path("/set_config").Methods("POST").HandlerFunc(setConfig)
|
||||
configMap = make(map[string]configItem)
|
||||
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
|
||||
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e ""
|
||||
sleep 1
|
||||
insert_cmd="insert into unibase.haha(id, name) values(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
||||
insert_cmd="insert into unibase.haha(id, name) values(10, 'aaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||
insert_cmd="$insert_cmd,(10, 'aaaa')"
|
||||
mysql -h$mysql_host -P$mysql_port -u$user_name -p$passwd sniffer -e "$insert_cmd"
|
||||
sleep 1
|
||||
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 (
|
||||
ComAuth = 141
|
||||
maxSQLLen = 5*1024*1024
|
||||
)
|
||||
|
||||
// Client information.
|
||||
|
|
|
@ -24,6 +24,7 @@ type MysqlSession struct {
|
|||
packageOffset int64
|
||||
expectReceiveSize int
|
||||
coverRanges []*jigsaw
|
||||
coverRange *jigsaw
|
||||
expectSendSize int
|
||||
prepareInfo *prepareInfo
|
||||
cachedPrepareStmt map[int][]byte
|
||||
|
@ -137,6 +138,7 @@ func mergeRanges(currRange *jigsaw, pkgRanges []*jigsaw) (mergedRange *jigsaw, n
|
|||
return currRange, newPkgRanges
|
||||
|
||||
} else if len(pkgRanges) == 1 {
|
||||
//
|
||||
nextRange = pkgRanges[0]
|
||||
|
||||
} else {
|
||||
|
@ -253,6 +255,7 @@ func (ms *MysqlSession) GenerateQueryPiece() (qp model.QueryPiece) {
|
|||
ms.expectSendSize = 0
|
||||
ms.prepareInfo = nil
|
||||
ms.coverRanges = make([]*jigsaw, 0, 4)
|
||||
ms.coverRange = nil
|
||||
ms.lastSeq = -1
|
||||
ms.ignoreAckID = -1
|
||||
ms.sendSize = 0
|
||||
|
@ -267,6 +270,11 @@ func (ms *MysqlSession) GenerateQueryPiece() (qp model.QueryPiece) {
|
|||
return
|
||||
}
|
||||
|
||||
if len(ms.cachedStmtBytes) > maxSQLLen {
|
||||
log.Warn("sql in cache is too long, ignore it")
|
||||
return
|
||||
}
|
||||
|
||||
var mqp *model.PooledMysqlQueryPiece
|
||||
var querySQLInBytes []byte
|
||||
if ms.cachedStmtBytes[0] > 32 {
|
||||
|
@ -327,7 +335,6 @@ func (ms *MysqlSession) GenerateQueryPiece() (qp model.QueryPiece) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if strictMode && mqp != nil && mqp.VisitUser == nil {
|
||||
user, db, err := querySessionInfo(ms.serverPort, mqp.SessionID)
|
||||
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