From c9f514feed29d33371746f7ef422aca84a976f94 Mon Sep 17 00:00:00 2001 From: hebo Date: Thu, 5 Sep 2019 20:27:49 +0800 Subject: [PATCH] add communicator api --- Godeps/Godeps.json | 4 + capture/config.go | 9 +- capture/network.go | 16 + communicator/config.go | 45 +-- communicator/controller.go | 83 ++++ communicator/major_function.go | 27 ++ communicator/model.go | 36 ++ communicator/url.go | 7 + scripts/generate_mysql_select.sh | 10 +- session-dealer/mysql/const.go | 2 +- session-dealer/mysql/session.go | 9 +- .../github.com/zr-hebo/validator/.gitignore | 23 ++ .../github.com/zr-hebo/validator/.travis.yml | 10 + vendor/github.com/zr-hebo/validator/LICENSE | 201 ++++++++++ vendor/github.com/zr-hebo/validator/README.md | 167 ++++++++ .../github.com/zr-hebo/validator/builtins.go | 246 ++++++++++++ vendor/github.com/zr-hebo/validator/doc.go | 265 +++++++++++++ .../github.com/zr-hebo/validator/validator.go | 372 ++++++++++++++++++ 18 files changed, 1490 insertions(+), 42 deletions(-) create mode 100644 communicator/controller.go create mode 100644 communicator/major_function.go create mode 100644 communicator/model.go create mode 100644 communicator/url.go create mode 100644 vendor/github.com/zr-hebo/validator/.gitignore create mode 100644 vendor/github.com/zr-hebo/validator/.travis.yml create mode 100644 vendor/github.com/zr-hebo/validator/LICENSE create mode 100644 vendor/github.com/zr-hebo/validator/README.md create mode 100644 vendor/github.com/zr-hebo/validator/builtins.go create mode 100644 vendor/github.com/zr-hebo/validator/doc.go create mode 100644 vendor/github.com/zr-hebo/validator/validator.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1257b64..ae85e37 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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" diff --git a/capture/config.go b/capture/config.go index 5fac129..328f9c0 100644 --- a/capture/config.go +++ b/capture/config.go @@ -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()) } diff --git a/capture/network.go b/capture/network.go index a6566fd..ace64f2 100644 --- a/capture/network.go +++ b/capture/network.go @@ -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 diff --git a/communicator/config.go b/communicator/config.go index 14f0371..5a0716a 100644 --- a/communicator/config.go +++ b/communicator/config.go @@ -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" -} - diff --git a/communicator/controller.go b/communicator/controller.go new file mode 100644 index 0000000..768b6e9 --- /dev/null +++ b/communicator/controller.go @@ -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) +} diff --git a/communicator/major_function.go b/communicator/major_function.go new file mode 100644 index 0000000..777a263 --- /dev/null +++ b/communicator/major_function.go @@ -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() +} diff --git a/communicator/model.go b/communicator/model.go new file mode 100644 index 0000000..56501a1 --- /dev/null +++ b/communicator/model.go @@ -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 +} diff --git a/communicator/url.go b/communicator/url.go new file mode 100644 index 0000000..d0dc6cd --- /dev/null +++ b/communicator/url.go @@ -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) +} \ No newline at end of file diff --git a/scripts/generate_mysql_select.sh b/scripts/generate_mysql_select.sh index 498cffa..e31fab7 100755 --- a/scripts/generate_mysql_select.sh +++ b/scripts/generate_mysql_select.sh @@ -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" diff --git a/session-dealer/mysql/const.go b/session-dealer/mysql/const.go index b4afad0..a6ac377 100644 --- a/session-dealer/mysql/const.go +++ b/session-dealer/mysql/const.go @@ -41,7 +41,7 @@ const ( ) const ( - ComAuth = 141 + maxSQLLen = 5*1024*1024 ) // Client information. diff --git a/session-dealer/mysql/session.go b/session-dealer/mysql/session.go index 16410e8..cedc591 100644 --- a/session-dealer/mysql/session.go +++ b/session-dealer/mysql/session.go @@ -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 { diff --git a/vendor/github.com/zr-hebo/validator/.gitignore b/vendor/github.com/zr-hebo/validator/.gitignore new file mode 100644 index 0000000..8365624 --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/.gitignore @@ -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 diff --git a/vendor/github.com/zr-hebo/validator/.travis.yml b/vendor/github.com/zr-hebo/validator/.travis.yml new file mode 100644 index 0000000..92879e6 --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/.travis.yml @@ -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 diff --git a/vendor/github.com/zr-hebo/validator/LICENSE b/vendor/github.com/zr-hebo/validator/LICENSE new file mode 100644 index 0000000..ad410e1 --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/LICENSE @@ -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. \ No newline at end of file diff --git a/vendor/github.com/zr-hebo/validator/README.md b/vendor/github.com/zr-hebo/validator/README.md new file mode 100644 index 0000000..4516441 --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/README.md @@ -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 + +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. diff --git a/vendor/github.com/zr-hebo/validator/builtins.go b/vendor/github.com/zr-hebo/validator/builtins.go new file mode 100644 index 0000000..c024ab5 --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/builtins.go @@ -0,0 +1,246 @@ +// Package validator implements value validations +// +// Copyright 2014 Roberto Teixeira +// +// 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 +} diff --git a/vendor/github.com/zr-hebo/validator/doc.go b/vendor/github.com/zr-hebo/validator/doc.go new file mode 100644 index 0000000..5dde50d --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/doc.go @@ -0,0 +1,265 @@ +// Package validator implements value validations +// +// Copyright 2014 Roberto Teixeira +// +// 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 diff --git a/vendor/github.com/zr-hebo/validator/validator.go b/vendor/github.com/zr-hebo/validator/validator.go new file mode 100644 index 0000000..376109d --- /dev/null +++ b/vendor/github.com/zr-hebo/validator/validator.go @@ -0,0 +1,372 @@ +// Package validator implements value validations +// +// Copyright 2014 Roberto Teixeira +// +// 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 +}