add communicator api

This commit is contained in:
hebo 2019-09-05 20:27:49 +08:00
parent d06ca1131e
commit c9f514feed
18 changed files with 1490 additions and 42 deletions

4
Godeps/Godeps.json generated
View File

@ -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"

View File

@ -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())
}

View File

@ -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

View File

@ -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"
}

View File

@ -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)
}

View File

@ -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()
}

36
communicator/model.go Normal file
View File

@ -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
}

7
communicator/url.go Normal file
View File

@ -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)
}

View File

@ -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"

View File

@ -41,7 +41,7 @@ const (
)
const (
ComAuth = 141
maxSQLLen = 5*1024*1024
)
// Client information.

View File

@ -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 {

23
vendor/github.com/zr-hebo/validator/.gitignore generated vendored Normal file
View File

@ -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

10
vendor/github.com/zr-hebo/validator/.travis.yml generated vendored Normal file
View File

@ -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

201
vendor/github.com/zr-hebo/validator/LICENSE generated vendored Normal file
View File

@ -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.

167
vendor/github.com/zr-hebo/validator/README.md generated vendored Normal file
View File

@ -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.

246
vendor/github.com/zr-hebo/validator/builtins.go generated vendored Normal file
View File

@ -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
}

265
vendor/github.com/zr-hebo/validator/doc.go generated vendored Normal file
View File

@ -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

372
vendor/github.com/zr-hebo/validator/validator.go generated vendored Normal file
View File

@ -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
}