feat(Go-Tool):2020/12/14:添加validator.v8源码解析和使用示例
This commit is contained in:
Generated
+1
@@ -3,6 +3,7 @@
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<content url="file://$MODULE_DIR$/vendor/gopkg.in/go-playground/validator.v8" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// implementation; the file example_pq_test.go has the complete source.
|
||||
//
|
||||
//read note 拷贝一个代码过来解析.感觉在go里面拷贝代码简单多了,依赖可以直接使用。我在这边使用read note(自定义todo标识)来标识我的解析.
|
||||
package SourceAnalysis
|
||||
package sourceAnalysis
|
||||
|
||||
import "sort"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
// // do something with e.Value
|
||||
// }
|
||||
//
|
||||
package SourceAnalysis
|
||||
package sourceAnalysis
|
||||
|
||||
// Element is an element of a linked list.
|
||||
//read note list中对应的元素,list相当是通过一个链表来链接所有的Element元素.
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ring implements operations on circular lists.
|
||||
package SourceAnalysis
|
||||
package sourceAnalysis
|
||||
|
||||
// A Ring is an element of a circular list, or ring.
|
||||
// Rings do not have a beginning or end; a pointer to any ring element
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
@@ -0,0 +1,5 @@
|
||||
我的分析文章可参考:[Go源码解析:validator.v8](https://blog.csdn.net/qq_34326321/article/details/111030128)
|
||||
|
||||
流程图: 
|
||||
|
||||
流程图地址:[地址](https://www.processon.com/view/link/5fd6dabb63768906e6db0d25)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.test
|
||||
*.out
|
||||
*.txt
|
||||
cover.html
|
||||
README.html
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dean Karn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
Package validator
|
||||
================
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png">[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
[](https://semaphoreci.com/joeybloggs/validator)
|
||||
[](https://coveralls.io/github/go-playground/validator?branch=v8)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/validator)
|
||||
[](https://godoc.org/gopkg.in/go-playground/validator.v8)
|
||||

|
||||
|
||||
Package validator implements value validations for structs and individual fields based on tags.
|
||||
|
||||
It has the following **unique** features:
|
||||
|
||||
- Cross Field and Cross Struct validations by using validation tags or custom validators.
|
||||
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
|
||||
- Handles type interface by determining it's underlying type prior to validation.
|
||||
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
|
||||
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
|
||||
- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use go get.
|
||||
|
||||
go get gopkg.in/go-playground/validator.v8
|
||||
|
||||
or to update
|
||||
|
||||
go get -u gopkg.in/go-playground/validator.v8
|
||||
|
||||
Then import the validator package into your own code.
|
||||
|
||||
import "gopkg.in/go-playground/validator.v8"
|
||||
|
||||
Error Return Value
|
||||
-------
|
||||
|
||||
Validation functions return type error
|
||||
|
||||
They return type error to avoid the issue discussed in the following, where err is always != nil:
|
||||
|
||||
* http://stackoverflow.com/a/29138676/3158232
|
||||
* https://github.com/go-playground/validator/issues/134
|
||||
|
||||
validator only returns nil or ValidationErrors as type error; so in you code all you need to do
|
||||
is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors
|
||||
like so:
|
||||
|
||||
```go
|
||||
err := validate.Struct(mystruct)
|
||||
validationErrors := err.(validator.ValidationErrors)
|
||||
```
|
||||
|
||||
Usage and documentation
|
||||
------
|
||||
|
||||
Please see http://godoc.org/gopkg.in/go-playground/validator.v8 for detailed usage docs.
|
||||
|
||||
##### Examples:
|
||||
|
||||
Struct & Field validation
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `validate:"required"`
|
||||
LastName string `validate:"required"`
|
||||
Age uint8 `validate:"gte=0,lte=130"`
|
||||
Email string `validate:"required,email"`
|
||||
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
|
||||
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
|
||||
}
|
||||
|
||||
// Address houses a users address information
|
||||
type Address struct {
|
||||
Street string `validate:"required"`
|
||||
City string `validate:"required"`
|
||||
Planet string `validate:"required"`
|
||||
Phone string `validate:"required"`
|
||||
}
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
|
||||
validateStruct()
|
||||
validateField()
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "Badger",
|
||||
LastName: "Smith",
|
||||
Age: 135,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
|
||||
// Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
|
||||
err := errs.(validator.ValidationErrors)["User.Addresses[0].City"]
|
||||
fmt.Println(err.Field) // output: City
|
||||
fmt.Println(err.Tag) // output: required
|
||||
fmt.Println(err.Kind) // output: string
|
||||
fmt.Println(err.Type) // output: string
|
||||
fmt.Println(err.Param) // output:
|
||||
fmt.Println(err.Value) // output:
|
||||
|
||||
// from here you can create your own error messages in whatever language you wish
|
||||
return
|
||||
}
|
||||
|
||||
// save user to database
|
||||
}
|
||||
|
||||
func validateField() {
|
||||
myEmail := "joeybloggs.gmail.com"
|
||||
|
||||
errs := validate.Field(myEmail, "required,email")
|
||||
|
||||
if errs != nil {
|
||||
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
|
||||
return
|
||||
}
|
||||
|
||||
// email ok, move on
|
||||
}
|
||||
```
|
||||
|
||||
Custom Field Type
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// DbBackedUser User struct
|
||||
type DbBackedUser struct {
|
||||
Name sql.NullString `validate:"required"`
|
||||
Age sql.NullInt64 `validate:"required"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate := validator.New(config)
|
||||
|
||||
// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
|
||||
validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
|
||||
|
||||
x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
|
||||
errs := validate.Struct(x)
|
||||
|
||||
if len(errs.(validator.ValidationErrors)) > 0 {
|
||||
fmt.Printf("Errs:\n%+v\n", errs)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateValuer implements validator.CustomTypeFunc
|
||||
func ValidateValuer(field reflect.Value) interface{} {
|
||||
if valuer, ok := field.Interface().(driver.Valuer); ok {
|
||||
val, err := valuer.Value()
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
// handle the error how you want
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Struct Level Validation
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// User contains user information
|
||||
type User struct {
|
||||
FirstName string `json:"fname"`
|
||||
LastName string `json:"lname"`
|
||||
Age uint8 `validate:"gte=0,lte=130"`
|
||||
Email string `validate:"required,email"`
|
||||
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
|
||||
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
|
||||
}
|
||||
|
||||
// Address houses a users address information
|
||||
type Address struct {
|
||||
Street string `validate:"required"`
|
||||
City string `validate:"required"`
|
||||
Planet string `validate:"required"`
|
||||
Phone string `validate:"required"`
|
||||
}
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func main() {
|
||||
|
||||
config := &validator.Config{TagName: "validate"}
|
||||
|
||||
validate = validator.New(config)
|
||||
validate.RegisterStructValidation(UserStructLevelValidation, User{})
|
||||
|
||||
validateStruct()
|
||||
}
|
||||
|
||||
// UserStructLevelValidation contains custom struct level validations that don't always
|
||||
// make sense at the field validation level. For Example this function validates that either
|
||||
// FirstName or LastName exist; could have done that with a custom field validation but then
|
||||
// would have had to add it to both fields duplicating the logic + overhead, this way it's
|
||||
// only validated once.
|
||||
//
|
||||
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
|
||||
// hooks right into validator and you can combine with validation tags and still have a
|
||||
// common error output format.
|
||||
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||
|
||||
user := structLevel.CurrentStruct.Interface().(User)
|
||||
|
||||
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
|
||||
structLevel.ReportError(reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname")
|
||||
structLevel.ReportError(reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname")
|
||||
}
|
||||
|
||||
// plus can to more, even with different tag than "fnameorlname"
|
||||
}
|
||||
|
||||
func validateStruct() {
|
||||
|
||||
address := &Address{
|
||||
Street: "Eavesdown Docks",
|
||||
Planet: "Persphone",
|
||||
Phone: "none",
|
||||
City: "Unknown",
|
||||
}
|
||||
|
||||
user := &User{
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Age: 45,
|
||||
Email: "Badger.Smith@gmail.com",
|
||||
FavouriteColor: "#000",
|
||||
Addresses: []*Address{address},
|
||||
}
|
||||
|
||||
// returns nil or ValidationErrors ( map[string]*FieldError )
|
||||
errs := validate.Struct(user)
|
||||
|
||||
if errs != nil {
|
||||
|
||||
fmt.Println(errs) // output: Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag
|
||||
// Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag
|
||||
err := errs.(validator.ValidationErrors)["User.FirstName"]
|
||||
fmt.Println(err.Field) // output: FirstName
|
||||
fmt.Println(err.Tag) // output: fnameorlname
|
||||
fmt.Println(err.Kind) // output: string
|
||||
fmt.Println(err.Type) // output: string
|
||||
fmt.Println(err.Param) // output:
|
||||
fmt.Println(err.Value) // output:
|
||||
|
||||
// from here you can create your own error messages in whatever language you wish
|
||||
return
|
||||
}
|
||||
|
||||
// save user to database
|
||||
}
|
||||
```
|
||||
|
||||
Benchmarks
|
||||
------
|
||||
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.5.3 darwin/amd64
|
||||
```go
|
||||
PASS
|
||||
BenchmarkFieldSuccess-8 20000000 118 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFieldFailure-8 2000000 758 ns/op 432 B/op 4 allocs/op
|
||||
BenchmarkFieldDiveSuccess-8 500000 2471 ns/op 464 B/op 28 allocs/op
|
||||
BenchmarkFieldDiveFailure-8 500000 3172 ns/op 896 B/op 32 allocs/op
|
||||
BenchmarkFieldCustomTypeSuccess-8 5000000 300 ns/op 32 B/op 2 allocs/op
|
||||
BenchmarkFieldCustomTypeFailure-8 2000000 775 ns/op 432 B/op 4 allocs/op
|
||||
BenchmarkFieldOrTagSuccess-8 1000000 1122 ns/op 4 B/op 1 allocs/op
|
||||
BenchmarkFieldOrTagFailure-8 1000000 1167 ns/op 448 B/op 6 allocs/op
|
||||
BenchmarkStructLevelValidationSuccess-8 3000000 548 ns/op 160 B/op 5 allocs/op
|
||||
BenchmarkStructLevelValidationFailure-8 3000000 558 ns/op 160 B/op 5 allocs/op
|
||||
BenchmarkStructSimpleCustomTypeSuccess-8 2000000 623 ns/op 36 B/op 3 allocs/op
|
||||
BenchmarkStructSimpleCustomTypeFailure-8 1000000 1381 ns/op 640 B/op 9 allocs/op
|
||||
BenchmarkStructPartialSuccess-8 1000000 1036 ns/op 272 B/op 9 allocs/op
|
||||
BenchmarkStructPartialFailure-8 1000000 1734 ns/op 730 B/op 14 allocs/op
|
||||
BenchmarkStructExceptSuccess-8 2000000 888 ns/op 250 B/op 7 allocs/op
|
||||
BenchmarkStructExceptFailure-8 1000000 1036 ns/op 272 B/op 9 allocs/op
|
||||
BenchmarkStructSimpleCrossFieldSuccess-8 2000000 773 ns/op 80 B/op 4 allocs/op
|
||||
BenchmarkStructSimpleCrossFieldFailure-8 1000000 1487 ns/op 536 B/op 9 allocs/op
|
||||
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 1000000 1261 ns/op 112 B/op 7 allocs/op
|
||||
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 2055 ns/op 576 B/op 12 allocs/op
|
||||
BenchmarkStructSimpleSuccess-8 3000000 519 ns/op 4 B/op 1 allocs/op
|
||||
BenchmarkStructSimpleFailure-8 1000000 1429 ns/op 640 B/op 9 allocs/op
|
||||
BenchmarkStructSimpleSuccessParallel-8 10000000 146 ns/op 4 B/op 1 allocs/op
|
||||
BenchmarkStructSimpleFailureParallel-8 2000000 551 ns/op 640 B/op 9 allocs/op
|
||||
BenchmarkStructComplexSuccess-8 500000 3269 ns/op 244 B/op 15 allocs/op
|
||||
BenchmarkStructComplexFailure-8 200000 8436 ns/op 3609 B/op 60 allocs/op
|
||||
BenchmarkStructComplexSuccessParallel-8 1000000 1024 ns/op 244 B/op 15 allocs/op
|
||||
BenchmarkStructComplexFailureParallel-8 500000 3536 ns/op 3609 B/op 60 allocs/op
|
||||
```
|
||||
|
||||
Complimentary Software
|
||||
----------------------
|
||||
|
||||
Here is a list of software that compliments using this library either pre or post validation.
|
||||
|
||||
* [form](https://github.com/go-playground/form) - Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values. Dual Array and Full map support.
|
||||
* [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags.
|
||||
|
||||
How to Contribute
|
||||
------
|
||||
|
||||
There will always be a development branch for each version i.e. `v1-development`. In order to contribute,
|
||||
please make your pull requests against those branches.
|
||||
|
||||
If the changes being proposed or requested are breaking changes, please create an issue, for discussion
|
||||
or create a pull request against the highest development branch for example this package has a
|
||||
v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet.
|
||||
|
||||
I strongly encourage everyone whom creates a custom validation function to contribute them and
|
||||
help make this package even better.
|
||||
|
||||
License
|
||||
------
|
||||
Distributed under MIT License, please see license file in code for more details.
|
||||
+1429
File diff suppressed because it is too large
Load Diff
+285
@@ -0,0 +1,285 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type tagType uint8
|
||||
|
||||
const (
|
||||
typeDefault tagType = iota
|
||||
typeOmitEmpty
|
||||
typeNoStructLevel
|
||||
typeStructOnly
|
||||
typeDive
|
||||
typeOr
|
||||
typeExists
|
||||
)
|
||||
|
||||
type structCache struct {
|
||||
lock sync.Mutex
|
||||
m atomic.Value // map[reflect.Type]*cStruct
|
||||
}
|
||||
|
||||
func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
|
||||
c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *structCache) Set(key reflect.Type, value *cStruct) {
|
||||
|
||||
m := sc.m.Load().(map[reflect.Type]*cStruct)
|
||||
|
||||
nm := make(map[reflect.Type]*cStruct, len(m)+1)
|
||||
for k, v := range m {
|
||||
nm[k] = v
|
||||
}
|
||||
nm[key] = value
|
||||
sc.m.Store(nm)
|
||||
}
|
||||
|
||||
type tagCache struct {
|
||||
lock sync.Mutex
|
||||
m atomic.Value // map[string]*cTag
|
||||
}
|
||||
|
||||
func (tc *tagCache) Get(key string) (c *cTag, found bool) {
|
||||
c, found = tc.m.Load().(map[string]*cTag)[key]
|
||||
return
|
||||
}
|
||||
|
||||
func (tc *tagCache) Set(key string, value *cTag) {
|
||||
|
||||
m := tc.m.Load().(map[string]*cTag)
|
||||
|
||||
nm := make(map[string]*cTag, len(m)+1)
|
||||
for k, v := range m {
|
||||
nm[k] = v
|
||||
}
|
||||
nm[key] = value
|
||||
tc.m.Store(nm)
|
||||
}
|
||||
|
||||
type cStruct struct {
|
||||
Name string //结构体名称
|
||||
fields map[int]*cField //结构体对应的字段map
|
||||
fn StructLevelFunc //结构体校验器 (结构体类型->结构体校验器)
|
||||
}
|
||||
|
||||
type cField struct {
|
||||
Idx int //字段下标
|
||||
Name string //字段名
|
||||
AltName string //
|
||||
cTags *cTag //Field对应的cTag规则,是一个链表(一串的规则).
|
||||
}
|
||||
|
||||
type cTag struct {
|
||||
tag string //标签
|
||||
aliasTag string //
|
||||
actualAliasTag string //
|
||||
param string //如果是比较类型的标签,这里存放的是比较的值,比如说 min=10,这里存放的是【10】这个值
|
||||
hasAlias bool //是否有别名校验器标签
|
||||
typeof tagType //对应的tagType
|
||||
hasTag bool //是否存在tag标签
|
||||
fn Func //当前cTag对应的【tag标签校验器】
|
||||
next *cTag //下一个cTag标签
|
||||
}
|
||||
|
||||
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
|
||||
|
||||
v.structCache.lock.Lock()
|
||||
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
|
||||
|
||||
typ := current.Type()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures struct
|
||||
// isn't parsed again.
|
||||
//read note 从缓存里面获取对应结构体类型的处理.
|
||||
cs, ok := v.structCache.Get(typ)
|
||||
if ok {
|
||||
return cs
|
||||
}
|
||||
|
||||
//read note 如果缓存里面拿不到的话,就要从结构的Field中去解析
|
||||
cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]}
|
||||
|
||||
numFields := current.NumField()
|
||||
|
||||
var ctag *cTag
|
||||
var fld reflect.StructField
|
||||
var tag string
|
||||
var customName string
|
||||
|
||||
//read note 进行字段的处理
|
||||
for i := 0; i < numFields; i++ {
|
||||
|
||||
fld = typ.Field(i)
|
||||
|
||||
//read note 内嵌类型和小写的处理,直接pass
|
||||
// 类似这种的:
|
||||
/* type Derive struct {
|
||||
Base ---内嵌
|
||||
}
|
||||
*/
|
||||
if !fld.Anonymous && fld.PkgPath != blank {
|
||||
continue
|
||||
}
|
||||
|
||||
//read note 获取tag(通过之前Config设置的【tagName】属性去获取对应的tag)
|
||||
tag = fld.Tag.Get(v.tagName)
|
||||
|
||||
//read note 如果是忽略【-】的话,跳过
|
||||
if tag == skipValidationTag {
|
||||
continue
|
||||
}
|
||||
|
||||
customName = fld.Name
|
||||
|
||||
//read note config中【fieldNameTag】的处理,会设置到【cField】的【AltName】字段上,应该只是错误输出用的
|
||||
if v.fieldNameTag != blank {
|
||||
|
||||
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0]
|
||||
|
||||
// dash check is for json "-" (aka skipValidationTag) means don't output in json
|
||||
if name != "" && name != skipValidationTag {
|
||||
customName = name
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
|
||||
// and so only struct level caching can be used instead of combined with Field tag caching
|
||||
|
||||
//read note struct层的校验规则和Field层的校验规则不能共用(标签可能相同,但是别名会不一样..)
|
||||
|
||||
if len(tag) > 0 {
|
||||
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, blank, false)
|
||||
} else {
|
||||
// even if field doesn't have validations need cTag for traversing to potential inner/nested
|
||||
// elements of the field.
|
||||
ctag = new(cTag)
|
||||
}
|
||||
|
||||
cs.fields[i] = &cField{Idx: i, Name: fld.Name, AltName: customName, cTags: ctag}
|
||||
}
|
||||
|
||||
v.structCache.Set(typ, cs)
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
|
||||
|
||||
var t string
|
||||
var ok bool
|
||||
noAlias := len(alias) == 0
|
||||
//read note:这边会把tag根据【,】进行分割处理,得到对应的tag组,所以我们在写的时候可以写入,分割的标签
|
||||
tags := strings.Split(tag, tagSeparator)
|
||||
|
||||
for i := 0; i < len(tags); i++ {
|
||||
|
||||
t = tags[i]
|
||||
|
||||
if noAlias {
|
||||
alias = t
|
||||
}
|
||||
|
||||
//read note 如果有别名校验器,则回去查找别名校验器(第一个和后面的设置不一样.调用next和没有调用next的区别)
|
||||
if v.hasAliasValidators {
|
||||
// check map for alias and process new tags, otherwise process as usual
|
||||
if tagsVal, found := v.aliasValidators[t]; found {
|
||||
|
||||
if i == 0 {
|
||||
//read note 对别名校验器进行解析(一个子递归的过程.)除第一个校验器外,后面的校验器是通过一个next指向的链表连接起来
|
||||
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
|
||||
} else {
|
||||
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
|
||||
current.next, current = next, curr
|
||||
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//read note 设置对应的默认标签(第一个和后面的设置不一样.调用next和没有调用next的区别)
|
||||
if i == 0 {
|
||||
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
|
||||
firstCtag = current
|
||||
} else {
|
||||
current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
|
||||
current = current.next
|
||||
}
|
||||
|
||||
//read note 判断用 【,】分割后的标签,前面几个是处理特殊标签的.如果不是特殊标签,则往下处理设置tag的值
|
||||
switch t {
|
||||
case diveTag:
|
||||
current.typeof = typeDive
|
||||
continue
|
||||
|
||||
case omitempty:
|
||||
current.typeof = typeOmitEmpty
|
||||
continue
|
||||
|
||||
case structOnlyTag:
|
||||
current.typeof = typeStructOnly
|
||||
continue
|
||||
|
||||
case noStructLevelTag:
|
||||
current.typeof = typeNoStructLevel
|
||||
continue
|
||||
|
||||
case existsTag:
|
||||
current.typeof = typeExists
|
||||
continue
|
||||
|
||||
default:
|
||||
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
|
||||
orVals := strings.Split(t, orSeparator)
|
||||
|
||||
//read note 或的条件进行拆分.
|
||||
|
||||
for j := 0; j < len(orVals); j++ {
|
||||
//read note 解析【=】标签
|
||||
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
|
||||
|
||||
if noAlias {
|
||||
alias = vals[0]
|
||||
current.aliasTag = alias
|
||||
} else {
|
||||
current.actualAliasTag = t
|
||||
}
|
||||
|
||||
//read note 如果或的标签成立,也就是说 A|B|C ABC会组装成一个链(非头节点需要往下一个节点去处理,所以这边需要把current指向它的next)
|
||||
if j > 0 {
|
||||
current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
|
||||
current = current.next
|
||||
}
|
||||
|
||||
current.tag = vals[0]
|
||||
if len(current.tag) == 0 {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
|
||||
}
|
||||
|
||||
//read note 查找对应的校验规则
|
||||
if current.fn, ok = v.validationFuncs[current.tag]; !ok {
|
||||
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
|
||||
}
|
||||
|
||||
//read note 设置为typeOr.这个应该在后面处理对应字段的时候会拿到并且通过or的方式进行处理
|
||||
if len(orVals) > 1 {
|
||||
current.typeof = typeOr
|
||||
}
|
||||
|
||||
if len(vals) > 1 {
|
||||
current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+852
@@ -0,0 +1,852 @@
|
||||
/*
|
||||
Package validator implements value validations for structs and individual fields
|
||||
based on tags.
|
||||
|
||||
It can also handle Cross-Field and Cross-Struct validation for nested structs
|
||||
and has the ability to dive into arrays and maps of any type.
|
||||
|
||||
Why not a better error message?
|
||||
Because this library intends for you to handle your own error messages.
|
||||
|
||||
Why should I handle my own errors?
|
||||
Many reasons. We built an internationalized application and needed to know the
|
||||
field, and what validation failed so we could provide a localized error.
|
||||
|
||||
if fieldErr.Field == "Name" {
|
||||
switch fieldErr.ErrorTag
|
||||
case "required":
|
||||
return "Translated string based on field + error"
|
||||
default:
|
||||
return "Translated string based on field"
|
||||
}
|
||||
|
||||
|
||||
Validation Functions Return Type error
|
||||
|
||||
Doing things this way is actually the way the standard library does, see the
|
||||
file.Open method here:
|
||||
|
||||
https://golang.org/pkg/os/#Open.
|
||||
|
||||
The authors return type "error" to avoid the issue discussed in the following,
|
||||
where err is always != nil:
|
||||
|
||||
http://stackoverflow.com/a/29138676/3158232
|
||||
https://github.com/go-playground/validator/issues/134
|
||||
|
||||
Validator only returns nil or ValidationErrors as type error; so, in your code
|
||||
all you need to do is check if the error returned is not nil, and if it's not
|
||||
type cast it to type ValidationErrors like so err.(validator.ValidationErrors).
|
||||
|
||||
Custom Functions
|
||||
|
||||
Custom functions can be added. Example:
|
||||
|
||||
// Structure
|
||||
func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
|
||||
|
||||
if whatever {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
validate.RegisterValidation("custom tag name", customFunc)
|
||||
// NOTES: using the same tag name as an existing function
|
||||
// will overwrite the existing one
|
||||
|
||||
Cross-Field Validation
|
||||
|
||||
Cross-Field Validation can be done via the following tags:
|
||||
- eqfield
|
||||
- nefield
|
||||
- gtfield
|
||||
- gtefield
|
||||
- ltfield
|
||||
- ltefield
|
||||
- eqcsfield
|
||||
- necsfield
|
||||
- gtcsfield
|
||||
- ftecsfield
|
||||
- ltcsfield
|
||||
- ltecsfield
|
||||
|
||||
If, however, some custom cross-field validation is required, it can be done
|
||||
using a custom validation.
|
||||
|
||||
Why not just have cross-fields validation tags (i.e. only eqcsfield and not
|
||||
eqfield)?
|
||||
|
||||
The reason is efficiency. If you want to check a field within the same struct
|
||||
"eqfield" only has to find the field on the same struct (1 level). But, if we
|
||||
used "eqcsfield" it could be multiple levels down. Example:
|
||||
|
||||
type Inner struct {
|
||||
StartDate time.Time
|
||||
}
|
||||
|
||||
type Outer struct {
|
||||
InnerStructField *Inner
|
||||
CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"`
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
inner := &Inner{
|
||||
StartDate: now,
|
||||
}
|
||||
|
||||
outer := &Outer{
|
||||
InnerStructField: inner,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
errs := validate.Struct(outer)
|
||||
|
||||
// NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed
|
||||
// into the function
|
||||
// when calling validate.FieldWithValue(val, field, tag) val will be
|
||||
// whatever you pass, struct, field...
|
||||
// when calling validate.Field(field, tag) val will be nil
|
||||
|
||||
Multiple Validators
|
||||
|
||||
Multiple validators on a field will process in the order defined. Example:
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"max=10,min=1"`
|
||||
}
|
||||
|
||||
// max will be checked then min
|
||||
|
||||
Bad Validator definitions are not handled by the library. Example:
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"min=10,max=0"`
|
||||
}
|
||||
|
||||
// this definition of min max will never succeed
|
||||
|
||||
Using Validator Tags
|
||||
|
||||
Baked In Cross-Field validation only compares fields on the same struct.
|
||||
If Cross-Field + Cross-Struct validation is needed you should implement your
|
||||
own custom validator.
|
||||
|
||||
Comma (",") is the default separator of validation tags. If you wish to
|
||||
have a comma included within the parameter (i.e. excludesall=,) you will need to
|
||||
use the UTF-8 hex representation 0x2C, which is replaced in the code as a comma,
|
||||
so the above will become excludesall=0x2C.
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"excludesall=,"` // BAD! Do not include a comma.
|
||||
Field `validate:"excludesall=0x2C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
Pipe ("|") is the default separator of validation tags. If you wish to
|
||||
have a pipe included within the parameter i.e. excludesall=| you will need to
|
||||
use the UTF-8 hex representation 0x7C, which is replaced in the code as a pipe,
|
||||
so the above will become excludesall=0x7C
|
||||
|
||||
type Test struct {
|
||||
Field `validate:"excludesall=|"` // BAD! Do not include a a pipe!
|
||||
Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation.
|
||||
}
|
||||
|
||||
|
||||
Baked In Validators and Tags
|
||||
|
||||
Here is a list of the current built in validators:
|
||||
|
||||
|
||||
Skip Field
|
||||
|
||||
Tells the validation to skip this struct field; this is particularly
|
||||
handy in ignoring embedded structs from being validated. (Usage: -)
|
||||
Usage: -
|
||||
|
||||
|
||||
Or Operator
|
||||
|
||||
This is the 'or' operator allowing multiple validators to be used and
|
||||
accepted. (Usage: rbg|rgba) <-- this would allow either rgb or rgba
|
||||
colors to be accepted. This can also be combined with 'and' for example
|
||||
( Usage: omitempty,rgb|rgba)
|
||||
|
||||
Usage: |
|
||||
|
||||
StructOnly
|
||||
|
||||
When a field that is a nested struct is encountered, and contains this flag
|
||||
any validation on the nested struct will be run, but none of the nested
|
||||
struct fields will be validated. This is usefull if inside of you program
|
||||
you know the struct will be valid, but need to verify it has been assigned.
|
||||
NOTE: only "required" and "omitempty" can be used on a struct itself.
|
||||
|
||||
Usage: structonly
|
||||
|
||||
NoStructLevel
|
||||
|
||||
Same as structonly tag except that any struct level validations will not run.
|
||||
|
||||
Usage: nostructlevel
|
||||
|
||||
Exists
|
||||
|
||||
Is a special tag without a validation function attached. It is used when a field
|
||||
is a Pointer, Interface or Invalid and you wish to validate that it exists.
|
||||
Example: want to ensure a bool exists if you define the bool as a pointer and
|
||||
use exists it will ensure there is a value; couldn't use required as it would
|
||||
fail when the bool was false. exists will fail is the value is a Pointer, Interface
|
||||
or Invalid and is nil.
|
||||
|
||||
Usage: exists
|
||||
|
||||
Omit Empty
|
||||
|
||||
Allows conditional validation, for example if a field is not set with
|
||||
a value (Determined by the "required" validator) then other validation
|
||||
such as min or max won't run, but if a value is set validation will run.
|
||||
|
||||
Usage: omitempty
|
||||
|
||||
Dive
|
||||
|
||||
This tells the validator to dive into a slice, array or map and validate that
|
||||
level of the slice, array or map with the validation tags that follow.
|
||||
Multidimensional nesting is also supported, each level you wish to dive will
|
||||
require another dive tag.
|
||||
|
||||
Usage: dive
|
||||
|
||||
Example #1
|
||||
|
||||
[][]string with validation tag "gt=0,dive,len=1,dive,required"
|
||||
// gt=0 will be applied to []
|
||||
// len=1 will be applied to []string
|
||||
// required will be applied to string
|
||||
|
||||
Example #2
|
||||
|
||||
[][]string with validation tag "gt=0,dive,dive,required"
|
||||
// gt=0 will be applied to []
|
||||
// []string will be spared validation
|
||||
// required will be applied to string
|
||||
|
||||
Required
|
||||
|
||||
This validates that the value is not the data types default zero value.
|
||||
For numbers ensures value is not zero. For strings ensures value is
|
||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||
ensures the value is not nil.
|
||||
|
||||
Usage: required
|
||||
|
||||
Length
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
equal to the parameter given. For strings, it checks that
|
||||
the string length is exactly that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: len=10
|
||||
|
||||
Maximum
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
less than or equal to the parameter given. For strings, it checks
|
||||
that the string length is at most that number of characters. For
|
||||
slices, arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: max=10
|
||||
|
||||
Mininum
|
||||
|
||||
For numbers, min will ensure that the value is
|
||||
greater or equal to the parameter given. For strings, it checks that
|
||||
the string length is at least that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Usage: min=10
|
||||
|
||||
Equals
|
||||
|
||||
For strings & numbers, eq will ensure that the value is
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Usage: eq=10
|
||||
|
||||
Not Equal
|
||||
|
||||
For strings & numbers, ne will ensure that the value is not
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Usage: ne=10
|
||||
|
||||
Greater Than
|
||||
|
||||
For numbers, this will ensure that the value is greater than the
|
||||
parameter given. For strings, it checks that the string length
|
||||
is greater than that number of characters. For slices, arrays
|
||||
and maps it validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is greater than time.Now.UTC().
|
||||
|
||||
Usage: gt
|
||||
|
||||
Greater Than or Equal
|
||||
|
||||
Same as 'min' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gte=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is greater than or equal to time.Now.UTC().
|
||||
|
||||
Usage: gte
|
||||
|
||||
Less Than
|
||||
|
||||
For numbers, this will ensure that the value is less than the parameter given.
|
||||
For strings, it checks that the string length is less than that number of
|
||||
characters. For slices, arrays, and maps it validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: lt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
For time.Time ensures the time value is less than time.Now.UTC().
|
||||
|
||||
Usage: lt
|
||||
|
||||
Less Than or Equal
|
||||
|
||||
Same as 'max' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: lte=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is less than or equal to time.Now.UTC().
|
||||
|
||||
Usage: lte
|
||||
|
||||
Field Equals Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
a struct or passed in field.
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on Password field using:
|
||||
Usage: eqfield=ConfirmPassword
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(password, confirmpassword, "eqfield")
|
||||
|
||||
Field Equals Another Field (relative)
|
||||
|
||||
This does the same as eqfield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: eqcsfield=InnerStructField.Field)
|
||||
|
||||
Field Does Not Equal Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
a struct or passed in field.
|
||||
|
||||
Examples:
|
||||
|
||||
// Confirm two colors are not the same:
|
||||
//
|
||||
// Validation on Color field:
|
||||
Usage: nefield=Color2
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(color1, color2, "nefield")
|
||||
|
||||
Field Does Not Equal Another Field (relative)
|
||||
|
||||
This does the same as nefield except that it validates the field provided
|
||||
relative to the top level struct.
|
||||
|
||||
Usage: necsfield=InnerStructField.Field
|
||||
|
||||
Field Greater Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(gtfield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "gtfield")
|
||||
|
||||
|
||||
Field Greater Than Another Relative Field
|
||||
|
||||
This does the same as gtfield except that it validates the field provided
|
||||
relative to the top level struct.
|
||||
|
||||
Usage: gtcsfield=InnerStructField.Field
|
||||
|
||||
Field Greater Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(gtefield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "gtefield")
|
||||
|
||||
Field Greater Than or Equal To Another Relative Field
|
||||
|
||||
This does the same as gtefield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: gtecsfield=InnerStructField.Field
|
||||
|
||||
Less Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(ltfield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "ltfield")
|
||||
|
||||
Less Than Another Relative Field
|
||||
|
||||
This does the same as ltfield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: ltcsfield=InnerStructField.Field
|
||||
|
||||
Less Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
// Validation on End field using:
|
||||
validate.Struct Usage(ltefield=Start)
|
||||
|
||||
Example #2:
|
||||
|
||||
// Validating by field:
|
||||
validate.FieldWithValue(start, end, "ltefield")
|
||||
|
||||
Less Than or Equal To Another Relative Field
|
||||
|
||||
This does the same as ltefield except that it validates the field provided relative
|
||||
to the top level struct.
|
||||
|
||||
Usage: ltecsfield=InnerStructField.Field
|
||||
|
||||
Alpha Only
|
||||
|
||||
This validates that a string value contains alpha characters only
|
||||
|
||||
Usage: alpha
|
||||
|
||||
Alphanumeric
|
||||
|
||||
This validates that a string value contains alphanumeric characters only
|
||||
|
||||
Usage: alphanum
|
||||
|
||||
Numeric
|
||||
|
||||
This validates that a string value contains a basic numeric value.
|
||||
basic excludes exponents etc...
|
||||
|
||||
Usage: numeric
|
||||
|
||||
Hexadecimal String
|
||||
|
||||
This validates that a string value contains a valid hexadecimal.
|
||||
|
||||
Usage: hexadecimal
|
||||
|
||||
Hexcolor String
|
||||
|
||||
This validates that a string value contains a valid hex color including
|
||||
hashtag (#)
|
||||
|
||||
Usage: hexcolor
|
||||
|
||||
RGB String
|
||||
|
||||
This validates that a string value contains a valid rgb color
|
||||
|
||||
Usage: rgb
|
||||
|
||||
RGBA String
|
||||
|
||||
This validates that a string value contains a valid rgba color
|
||||
|
||||
Usage: rgba
|
||||
|
||||
HSL String
|
||||
|
||||
This validates that a string value contains a valid hsl color
|
||||
|
||||
Usage: hsl
|
||||
|
||||
HSLA String
|
||||
|
||||
This validates that a string value contains a valid hsla color
|
||||
|
||||
Usage: hsla
|
||||
|
||||
E-mail String
|
||||
|
||||
This validates that a string value contains a valid email
|
||||
This may not conform to all possibilities of any rfc standard, but neither
|
||||
does any email provider accept all posibilities.
|
||||
|
||||
Usage: email
|
||||
|
||||
URL String
|
||||
|
||||
This validates that a string value contains a valid url
|
||||
This will accept any url the golang request uri accepts but must contain
|
||||
a schema for example http:// or rtmp://
|
||||
|
||||
Usage: url
|
||||
|
||||
URI String
|
||||
|
||||
This validates that a string value contains a valid uri
|
||||
This will accept any uri the golang request uri accepts
|
||||
|
||||
Usage: uri
|
||||
|
||||
Base64 String
|
||||
|
||||
This validates that a string value contains a valid base64 value.
|
||||
Although an empty string is valid base64 this will report an empty string
|
||||
as an error, if you wish to accept an empty string as valid you can use
|
||||
this with the omitempty tag.
|
||||
|
||||
Usage: base64
|
||||
|
||||
Contains
|
||||
|
||||
This validates that a string value contains the substring value.
|
||||
|
||||
Usage: contains=@
|
||||
|
||||
Contains Any
|
||||
|
||||
This validates that a string value contains any Unicode code points
|
||||
in the substring value.
|
||||
|
||||
Usage: containsany=!@#?
|
||||
|
||||
Contains Rune
|
||||
|
||||
This validates that a string value contains the supplied rune value.
|
||||
|
||||
Usage: containsrune=@
|
||||
|
||||
Excludes
|
||||
|
||||
This validates that a string value does not contain the substring value.
|
||||
|
||||
Usage: excludes=@
|
||||
|
||||
Excludes All
|
||||
|
||||
This validates that a string value does not contain any Unicode code
|
||||
points in the substring value.
|
||||
|
||||
Usage: excludesall=!@#?
|
||||
|
||||
Excludes Rune
|
||||
|
||||
This validates that a string value does not contain the supplied rune value.
|
||||
|
||||
Usage: excludesrune=@
|
||||
|
||||
International Standard Book Number
|
||||
|
||||
This validates that a string value contains a valid isbn10 or isbn13 value.
|
||||
|
||||
Usage: isbn
|
||||
|
||||
International Standard Book Number 10
|
||||
|
||||
This validates that a string value contains a valid isbn10 value.
|
||||
|
||||
Usage: isbn10
|
||||
|
||||
International Standard Book Number 13
|
||||
|
||||
This validates that a string value contains a valid isbn13 value.
|
||||
|
||||
Usage: isbn13
|
||||
|
||||
|
||||
Universally Unique Identifier UUID
|
||||
|
||||
This validates that a string value contains a valid UUID.
|
||||
|
||||
Usage: uuid
|
||||
|
||||
Universally Unique Identifier UUID v3
|
||||
|
||||
This validates that a string value contains a valid version 3 UUID.
|
||||
|
||||
Usage: uuid3
|
||||
|
||||
Universally Unique Identifier UUID v4
|
||||
|
||||
This validates that a string value contains a valid version 4 UUID.
|
||||
|
||||
Usage: uuid4
|
||||
|
||||
Universally Unique Identifier UUID v5
|
||||
|
||||
This validates that a string value contains a valid version 5 UUID.
|
||||
|
||||
Usage: uuid5
|
||||
|
||||
ASCII
|
||||
|
||||
This validates that a string value contains only ASCII characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: ascii
|
||||
|
||||
Printable ASCII
|
||||
|
||||
This validates that a string value contains only printable ASCII characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: asciiprint
|
||||
|
||||
Multi-Byte Characters
|
||||
|
||||
This validates that a string value contains one or more multibyte characters.
|
||||
NOTE: if the string is blank, this validates as true.
|
||||
|
||||
Usage: multibyte
|
||||
|
||||
Data URL
|
||||
|
||||
This validates that a string value contains a valid DataURI.
|
||||
NOTE: this will also validate that the data portion is valid base64
|
||||
|
||||
Usage: datauri
|
||||
|
||||
Latitude
|
||||
|
||||
This validates that a string value contains a valid latitude.
|
||||
|
||||
Usage: latitude
|
||||
|
||||
Longitude
|
||||
|
||||
This validates that a string value contains a valid longitude.
|
||||
|
||||
Usage: longitude
|
||||
|
||||
Social Security Number SSN
|
||||
|
||||
This validates that a string value contains a valid U.S. Social Security Number.
|
||||
|
||||
Usage: ssn
|
||||
|
||||
Internet Protocol Address IP
|
||||
|
||||
This validates that a string value contains a valid IP Adress.
|
||||
|
||||
Usage: ip
|
||||
|
||||
Internet Protocol Address IPv4
|
||||
|
||||
This validates that a string value contains a valid v4 IP Adress.
|
||||
|
||||
Usage: ipv4
|
||||
|
||||
Internet Protocol Address IPv6
|
||||
|
||||
This validates that a string value contains a valid v6 IP Adress.
|
||||
|
||||
Usage: ipv6
|
||||
|
||||
Classless Inter-Domain Routing CIDR
|
||||
|
||||
This validates that a string value contains a valid CIDR Adress.
|
||||
|
||||
Usage: cidr
|
||||
|
||||
Classless Inter-Domain Routing CIDRv4
|
||||
|
||||
This validates that a string value contains a valid v4 CIDR Adress.
|
||||
|
||||
Usage: cidrv4
|
||||
|
||||
Classless Inter-Domain Routing CIDRv6
|
||||
|
||||
This validates that a string value contains a valid v6 CIDR Adress.
|
||||
|
||||
Usage: cidrv6
|
||||
|
||||
Transmission Control Protocol Address TCP
|
||||
|
||||
This validates that a string value contains a valid resolvable TCP Adress.
|
||||
|
||||
Usage: tcp_addr
|
||||
|
||||
Transmission Control Protocol Address TCPv4
|
||||
|
||||
This validates that a string value contains a valid resolvable v4 TCP Adress.
|
||||
|
||||
Usage: tcp4_addr
|
||||
|
||||
Transmission Control Protocol Address TCPv6
|
||||
|
||||
This validates that a string value contains a valid resolvable v6 TCP Adress.
|
||||
|
||||
Usage: tcp6_addr
|
||||
|
||||
User Datagram Protocol Address UDP
|
||||
|
||||
This validates that a string value contains a valid resolvable UDP Adress.
|
||||
|
||||
Usage: udp_addr
|
||||
|
||||
User Datagram Protocol Address UDPv4
|
||||
|
||||
This validates that a string value contains a valid resolvable v4 UDP Adress.
|
||||
|
||||
Usage: udp4_addr
|
||||
|
||||
User Datagram Protocol Address UDPv6
|
||||
|
||||
This validates that a string value contains a valid resolvable v6 UDP Adress.
|
||||
|
||||
Usage: udp6_addr
|
||||
|
||||
Internet Protocol Address IP
|
||||
|
||||
This validates that a string value contains a valid resolvable IP Adress.
|
||||
|
||||
Usage: ip_addr
|
||||
|
||||
Internet Protocol Address IPv4
|
||||
|
||||
This validates that a string value contains a valid resolvable v4 IP Adress.
|
||||
|
||||
Usage: ip4_addr
|
||||
|
||||
Internet Protocol Address IPv6
|
||||
|
||||
This validates that a string value contains a valid resolvable v6 IP Adress.
|
||||
|
||||
Usage: ip6_addr
|
||||
|
||||
Unix domain socket end point Address
|
||||
|
||||
This validates that a string value contains a valid Unix Adress.
|
||||
|
||||
Usage: unix_addr
|
||||
|
||||
Media Access Control Address MAC
|
||||
|
||||
This validates that a string value contains a valid MAC Adress.
|
||||
|
||||
Usage: mac
|
||||
|
||||
Note: See Go's ParseMAC for accepted formats and types:
|
||||
|
||||
http://golang.org/src/net/mac.go?s=866:918#L29
|
||||
|
||||
Alias Validators and Tags
|
||||
|
||||
NOTE: When returning an error, the tag returned in "FieldError" will be
|
||||
the alias tag unless the dive tag is part of the alias. Everything after the
|
||||
dive tag is not reported as the alias tag. Also, the "ActualTag" in the before
|
||||
case will be the actual tag within the alias that failed.
|
||||
|
||||
Here is a list of the current built in alias tags:
|
||||
|
||||
"iscolor"
|
||||
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
|
||||
|
||||
Validator notes:
|
||||
|
||||
regex
|
||||
a regex validator won't be added because commas and = signs can be part
|
||||
of a regex which conflict with the validation definitions. Although
|
||||
workarounds can be made, they take away from using pure regex's.
|
||||
Furthermore it's quick and dirty but the regex's become harder to
|
||||
maintain and are not reusable, so it's as much a programming philosiphy
|
||||
as anything.
|
||||
|
||||
In place of this new validator functions should be created; a regex can
|
||||
be used within the validator function and even be precompiled for better
|
||||
efficiency within regexes.go.
|
||||
|
||||
And the best reason, you can submit a pull request and we can keep on
|
||||
adding to the validation library of this package!
|
||||
|
||||
Panics
|
||||
|
||||
This package panics when bad input is provided, this is by design, bad code like
|
||||
that should not make it to production.
|
||||
|
||||
type Test struct {
|
||||
TestField string `validate:"nonexistantfunction=1"`
|
||||
}
|
||||
|
||||
t := &Test{
|
||||
TestField: "Test"
|
||||
}
|
||||
|
||||
validate.Struct(t) // this will panic
|
||||
*/
|
||||
package validator
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+59
@@ -0,0 +1,59 @@
|
||||
package validator
|
||||
|
||||
import "regexp"
|
||||
|
||||
const (
|
||||
alphaRegexString = "^[a-zA-Z]+$"
|
||||
alphaNumericRegexString = "^[a-zA-Z0-9]+$"
|
||||
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
|
||||
numberRegexString = "^[0-9]+$"
|
||||
hexadecimalRegexString = "^[0-9a-fA-F]+$"
|
||||
hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
||||
rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
|
||||
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
|
||||
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
|
||||
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
|
||||
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
|
||||
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
aSCIIRegexString = "^[\x00-\x7F]*$"
|
||||
printableASCIIRegexString = "^[\x20-\x7E]*$"
|
||||
multibyteRegexString = "[^\x00-\x7F]"
|
||||
dataURIRegexString = "^data:.+\\/(.+);base64$"
|
||||
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
|
||||
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
|
||||
sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
|
||||
)
|
||||
|
||||
var (
|
||||
alphaRegex = regexp.MustCompile(alphaRegexString)
|
||||
alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString)
|
||||
numericRegex = regexp.MustCompile(numericRegexString)
|
||||
numberRegex = regexp.MustCompile(numberRegexString)
|
||||
hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString)
|
||||
hexcolorRegex = regexp.MustCompile(hexcolorRegexString)
|
||||
rgbRegex = regexp.MustCompile(rgbRegexString)
|
||||
rgbaRegex = regexp.MustCompile(rgbaRegexString)
|
||||
hslRegex = regexp.MustCompile(hslRegexString)
|
||||
hslaRegex = regexp.MustCompile(hslaRegexString)
|
||||
emailRegex = regexp.MustCompile(emailRegexString)
|
||||
base64Regex = regexp.MustCompile(base64RegexString)
|
||||
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
|
||||
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
|
||||
uUID3Regex = regexp.MustCompile(uUID3RegexString)
|
||||
uUID4Regex = regexp.MustCompile(uUID4RegexString)
|
||||
uUID5Regex = regexp.MustCompile(uUID5RegexString)
|
||||
uUIDRegex = regexp.MustCompile(uUIDRegexString)
|
||||
aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
|
||||
printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
|
||||
multibyteRegex = regexp.MustCompile(multibyteRegexString)
|
||||
dataURIRegex = regexp.MustCompile(dataURIRegexString)
|
||||
latitudeRegex = regexp.MustCompile(latitudeRegexString)
|
||||
longitudeRegex = regexp.MustCompile(longitudeRegexString)
|
||||
sSNRegex = regexp.MustCompile(sSNRegexString)
|
||||
)
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
blank = ""
|
||||
namespaceSeparator = "."
|
||||
leftBracket = "["
|
||||
rightBracket = "]"
|
||||
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
|
||||
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
||||
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
||||
)
|
||||
|
||||
var (
|
||||
restrictedTags = map[string]struct{}{
|
||||
diveTag: {},
|
||||
existsTag: {},
|
||||
structOnlyTag: {},
|
||||
omitempty: {},
|
||||
skipValidationTag: {},
|
||||
utf8HexComma: {},
|
||||
utf8Pipe: {},
|
||||
noStructLevelTag: {},
|
||||
}
|
||||
)
|
||||
|
||||
// ExtractType gets the actual underlying type of field value.
|
||||
// It will dive into pointers, customTypes and return you the
|
||||
// underlying value and it's kind.
|
||||
// it is exposed for use within you Custom Functions
|
||||
func (v *Validate) ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) {
|
||||
|
||||
val, k, _ := v.extractTypeInternal(current, false)
|
||||
return val, k
|
||||
}
|
||||
|
||||
// only exists to not break backward compatibility, needed to return the third param for a bug fix internally
|
||||
func (v *Validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
|
||||
|
||||
switch current.Kind() {
|
||||
case reflect.Ptr:
|
||||
|
||||
nullable = true
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Ptr, nullable
|
||||
}
|
||||
|
||||
return v.extractTypeInternal(current.Elem(), nullable)
|
||||
|
||||
case reflect.Interface:
|
||||
|
||||
nullable = true
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Interface, nullable
|
||||
}
|
||||
|
||||
return v.extractTypeInternal(current.Elem(), nullable)
|
||||
|
||||
case reflect.Invalid:
|
||||
return current, reflect.Invalid, nullable
|
||||
|
||||
default:
|
||||
|
||||
if v.hasCustomFuncs {
|
||||
|
||||
if fn, ok := v.customTypeFuncs[current.Type()]; ok {
|
||||
//read note 如果有自定义的类型校验器,则这边会先去调用一下类型校验器,然后在返回对应的结构体值,继续往回上一层,往下校验
|
||||
return v.extractTypeInternal(reflect.ValueOf(fn(current)), nullable)
|
||||
}
|
||||
}
|
||||
|
||||
return current, current.Kind(), nullable
|
||||
}
|
||||
}
|
||||
|
||||
// GetStructFieldOK traverses a struct to retrieve a specific field denoted by the provided namespace and
|
||||
// returns the field, field kind and whether is was successful in retrieving the field at all.
|
||||
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
|
||||
// could not be retrieved because it didn't exist.
|
||||
func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
|
||||
|
||||
current, kind := v.ExtractType(current)
|
||||
|
||||
if kind == reflect.Invalid {
|
||||
return current, kind, false
|
||||
}
|
||||
|
||||
if namespace == blank {
|
||||
return current, kind, true
|
||||
}
|
||||
|
||||
switch kind {
|
||||
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
|
||||
return current, kind, false
|
||||
|
||||
case reflect.Struct:
|
||||
|
||||
typ := current.Type()
|
||||
fld := namespace
|
||||
ns := namespace
|
||||
|
||||
if typ != timeType && typ != timePtrType {
|
||||
|
||||
idx := strings.Index(namespace, namespaceSeparator)
|
||||
|
||||
if idx != -1 {
|
||||
fld = namespace[:idx]
|
||||
ns = namespace[idx+1:]
|
||||
} else {
|
||||
ns = blank
|
||||
}
|
||||
|
||||
bracketIdx := strings.Index(fld, leftBracket)
|
||||
if bracketIdx != -1 {
|
||||
fld = fld[:bracketIdx]
|
||||
|
||||
ns = namespace[bracketIdx:]
|
||||
}
|
||||
|
||||
current = current.FieldByName(fld)
|
||||
|
||||
return v.GetStructFieldOK(current, ns)
|
||||
}
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
idx := strings.Index(namespace, leftBracket)
|
||||
idx2 := strings.Index(namespace, rightBracket)
|
||||
|
||||
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
|
||||
|
||||
if arrIdx >= current.Len() {
|
||||
return current, kind, false
|
||||
}
|
||||
|
||||
startIdx := idx2 + 1
|
||||
|
||||
if startIdx < len(namespace) {
|
||||
if namespace[startIdx:startIdx+1] == namespaceSeparator {
|
||||
startIdx++
|
||||
}
|
||||
}
|
||||
|
||||
return v.GetStructFieldOK(current.Index(arrIdx), namespace[startIdx:])
|
||||
|
||||
case reflect.Map:
|
||||
idx := strings.Index(namespace, leftBracket) + 1
|
||||
idx2 := strings.Index(namespace, rightBracket)
|
||||
|
||||
endIdx := idx2
|
||||
|
||||
if endIdx+1 < len(namespace) {
|
||||
if namespace[endIdx+1:endIdx+2] == namespaceSeparator {
|
||||
endIdx++
|
||||
}
|
||||
}
|
||||
|
||||
key := namespace[idx:idx2]
|
||||
|
||||
switch current.Type().Key().Kind() {
|
||||
case reflect.Int:
|
||||
i, _ := strconv.Atoi(key)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Int8:
|
||||
i, _ := strconv.ParseInt(key, 10, 8)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:])
|
||||
case reflect.Int16:
|
||||
i, _ := strconv.ParseInt(key, 10, 16)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:])
|
||||
case reflect.Int32:
|
||||
i, _ := strconv.ParseInt(key, 10, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:])
|
||||
case reflect.Int64:
|
||||
i, _ := strconv.ParseInt(key, 10, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Uint:
|
||||
i, _ := strconv.ParseUint(key, 10, 0)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint8:
|
||||
i, _ := strconv.ParseUint(key, 10, 8)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint16:
|
||||
i, _ := strconv.ParseUint(key, 10, 16)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint32:
|
||||
i, _ := strconv.ParseUint(key, 10, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:])
|
||||
case reflect.Uint64:
|
||||
i, _ := strconv.ParseUint(key, 10, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
|
||||
case reflect.Float32:
|
||||
f, _ := strconv.ParseFloat(key, 32)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:])
|
||||
case reflect.Float64:
|
||||
f, _ := strconv.ParseFloat(key, 64)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:])
|
||||
case reflect.Bool:
|
||||
b, _ := strconv.ParseBool(key)
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:])
|
||||
|
||||
// reflect.Type = string
|
||||
default:
|
||||
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:])
|
||||
}
|
||||
}
|
||||
|
||||
// if got here there was more namespace, cannot go any deeper
|
||||
panic("Invalid field namespace")
|
||||
}
|
||||
|
||||
// asInt returns the parameter as a int64
|
||||
// or panics if it can't convert
|
||||
func asInt(param string) int64 {
|
||||
|
||||
i, err := strconv.ParseInt(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asUint returns the parameter as a uint64
|
||||
// or panics if it can't convert
|
||||
func asUint(param string) uint64 {
|
||||
|
||||
i, err := strconv.ParseUint(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asFloat returns the parameter as a float64
|
||||
// or panics if it can't convert
|
||||
func asFloat(param string) float64 {
|
||||
|
||||
i, err := strconv.ParseFloat(param, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func panicIf(err error) {
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
+829
@@ -0,0 +1,829 @@
|
||||
/**
|
||||
* Package validator
|
||||
*
|
||||
* MISC:
|
||||
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
|
||||
*
|
||||
*/
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
utf8HexComma = "0x2C" //read note 在tag里面代表【,】;需要这么书写,直接写【,】会被识别成分割符
|
||||
utf8Pipe = "0x7C" //read note 在tag里面代表【|】;需要这么书写,直接写【|】会被识别成条件符
|
||||
tagSeparator = "," //read note tag标签分割符
|
||||
orSeparator = "|" //read note tag判断条件【或】
|
||||
tagKeySeparator = "=" //read note tag里面需要等值 使用=
|
||||
|
||||
structOnlyTag = "structonly" //read note 结构体上有的tagType,因为结构体的特殊性,他在获取cTag的时候是从第二个开始的
|
||||
_ // 也就是说第二个才是生效的,所以当只书写structonly的时候,他会略过这个tag,那么必须跟着前面的一个标签才行,比如说 `valid="require,structonly"`,这样才会生效
|
||||
noStructLevelTag = "nostructlevel" // 同上
|
||||
|
||||
omitempty = "omitempty" //read note 如果字段未设值则忽略它
|
||||
skipValidationTag = "-" //read note 忽略字段
|
||||
diveTag = "dive" //read note 深入到slice, array or map 里面去校验里面的字段是否正确,每一层都需要多一个【div】来标识
|
||||
_ //read note 比如说 [][]string "gt=0,dive,dive,required" gt是校验[][]string长度,第一个div是校验[]string,第二个div是校验string
|
||||
existsTag = "exists" //read note 校验值存在即可(除非是nil则会报错),和require的区别是说,require对默认值也会报错
|
||||
|
||||
//read note 数组结构的名称组成
|
||||
arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
|
||||
mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
|
||||
|
||||
//read note 字段校验错误信息打印
|
||||
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
|
||||
//read note 报错信息(非字段校验错误)
|
||||
invalidValidation = "Invalid validation tag on field %s"
|
||||
undefinedValidation = "Undefined validation function on field %s"
|
||||
validatorNotInitialized = "Validator instance not initialized"
|
||||
fieldNameRequired = "Field Name Required"
|
||||
tagRequired = "Tag Required"
|
||||
)
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
timePtrType = reflect.TypeOf(&time.Time{})
|
||||
defaultCField = new(cField)
|
||||
)
|
||||
|
||||
// StructLevel contains all of the information and helper methods
|
||||
// for reporting errors during struct level validation
|
||||
type StructLevel struct {
|
||||
TopStruct reflect.Value
|
||||
CurrentStruct reflect.Value
|
||||
errPrefix string
|
||||
nsPrefix string
|
||||
errs ValidationErrors
|
||||
v *Validate
|
||||
}
|
||||
|
||||
// ReportValidationErrors accepts the key relative to the top level struct and validatin errors.
|
||||
// Example: had a triple nested struct User, ContactInfo, Country and ran errs := validate.Struct(country)
|
||||
// from within a User struct level validation would call this method like so:
|
||||
// ReportValidationErrors("ContactInfo.", errs)
|
||||
// NOTE: relativeKey can contain both the Field Relative and Custom name relative paths
|
||||
// i.e. ReportValidationErrors("ContactInfo.|cInfo", errs) where cInfo represents say the JSON name of
|
||||
// the relative path; this will be split into 2 variables in the next valiator version.
|
||||
func (sl *StructLevel) ReportValidationErrors(relativeKey string, errs ValidationErrors) {
|
||||
for _, e := range errs {
|
||||
|
||||
idx := strings.Index(relativeKey, "|")
|
||||
var rel string
|
||||
var cRel string
|
||||
|
||||
if idx != -1 {
|
||||
rel = relativeKey[:idx]
|
||||
cRel = relativeKey[idx+1:]
|
||||
} else {
|
||||
rel = relativeKey
|
||||
}
|
||||
|
||||
key := sl.errPrefix + rel + e.Field
|
||||
|
||||
e.FieldNamespace = key
|
||||
e.NameNamespace = sl.nsPrefix + cRel + e.Name
|
||||
|
||||
sl.errs[key] = e
|
||||
}
|
||||
}
|
||||
|
||||
// ReportError reports an error just by passing the field and tag information
|
||||
// NOTE: tag can be an existing validation tag or just something you make up
|
||||
// and precess on the flip side it's up to you.
|
||||
func (sl *StructLevel) ReportError(field reflect.Value, fieldName string, customName string, tag string) {
|
||||
|
||||
field, kind := sl.v.ExtractType(field)
|
||||
|
||||
if fieldName == blank {
|
||||
panic(fieldNameRequired)
|
||||
}
|
||||
|
||||
if customName == blank {
|
||||
customName = fieldName
|
||||
}
|
||||
|
||||
if tag == blank {
|
||||
panic(tagRequired)
|
||||
}
|
||||
|
||||
ns := sl.errPrefix + fieldName
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
sl.errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: sl.nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: fieldName,
|
||||
Tag: tag,
|
||||
ActualTag: tag,
|
||||
Param: blank,
|
||||
Kind: kind,
|
||||
}
|
||||
default:
|
||||
sl.errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: sl.nsPrefix + customName,
|
||||
Name: customName,
|
||||
Field: fieldName,
|
||||
Tag: tag,
|
||||
ActualTag: tag,
|
||||
Param: blank,
|
||||
Value: field.Interface(),
|
||||
Kind: kind,
|
||||
Type: field.Type(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate contains the validator settings passed in using the Config struct
|
||||
type Validate struct {
|
||||
tagName string //校验起作用的tag名
|
||||
fieldNameTag string //
|
||||
validationFuncs map[string]Func //规则类型的校验 【tag标签】-> 校验规则
|
||||
structLevelFuncs map[reflect.Type]StructLevelFunc //规则结构体的校验 【结构体类型】-> 校验规则
|
||||
customTypeFuncs map[reflect.Type]CustomTypeFunc //类型校验器 【数据类型】-> 校验规则
|
||||
aliasValidators map[string]string //别名校验器 【别名匹配规则组合】-> 校验规则
|
||||
hasCustomFuncs bool //是否存在类型校验器
|
||||
hasAliasValidators bool //是否有别名校验器
|
||||
hasStructLevelFuncs bool //是否有结构体校验器
|
||||
tagCache *tagCache //tag对应的【校验规则方法】的缓存
|
||||
structCache *structCache //结构体对应的【校验规则方法】的缓存
|
||||
errsPool *sync.Pool //校验错误奖池
|
||||
}
|
||||
|
||||
func (v *Validate) initCheck() {
|
||||
if v == nil {
|
||||
panic(validatorNotInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
// Config contains the options that a Validator instance will use.
|
||||
// It is passed to the New() function
|
||||
type Config struct {
|
||||
TagName string
|
||||
FieldNameTag string
|
||||
}
|
||||
|
||||
// CustomTypeFunc allows for overriding or adding custom field type handler functions
|
||||
// field = field value of the type to return a value to be validated
|
||||
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
||||
type CustomTypeFunc func(field reflect.Value) interface{}
|
||||
|
||||
// Func accepts all values needed for file and cross field validation
|
||||
// v = validator instance, needed but some built in functions for it's custom types
|
||||
// topStruct = top level struct when validating by struct otherwise nil
|
||||
// currentStruct = current level struct when validating by struct otherwise optional comparison value
|
||||
// field = field value for validation
|
||||
// param = parameter used in validation i.e. gt=0 param would be 0
|
||||
type Func func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool
|
||||
|
||||
// StructLevelFunc accepts all values needed for struct level validation
|
||||
type StructLevelFunc func(v *Validate, structLevel *StructLevel)
|
||||
|
||||
// ValidationErrors is a type of map[string]*FieldError
|
||||
// it exists to allow for multiple errors to be passed from this library
|
||||
// and yet still subscribe to the error interface
|
||||
type ValidationErrors map[string]*FieldError
|
||||
|
||||
// Error is intended for use in development + debugging and not intended to be a production error message.
|
||||
// It allows ValidationErrors to subscribe to the Error interface.
|
||||
// All information to create an error message specific to your application is contained within
|
||||
// the FieldError found within the ValidationErrors map
|
||||
func (ve ValidationErrors) Error() string {
|
||||
|
||||
buff := bytes.NewBufferString(blank)
|
||||
|
||||
for key, err := range ve {
|
||||
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
|
||||
buff.WriteString("\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buff.String())
|
||||
}
|
||||
|
||||
// FieldError contains a single field's validation error along
|
||||
// with other properties that may be needed for error message creation
|
||||
type FieldError struct {
|
||||
FieldNamespace string
|
||||
NameNamespace string
|
||||
Field string
|
||||
Name string
|
||||
Tag string
|
||||
ActualTag string
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Param string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// New creates a new Validate instance for use.
|
||||
func New(config *Config) *Validate {
|
||||
|
||||
//read note tag校验器缓存,初始化
|
||||
tc := new(tagCache)
|
||||
tc.m.Store(make(map[string]*cTag))
|
||||
|
||||
//read note 结构体缓存,初始化一个Map,防止后面添加的时候报错
|
||||
sc := new(structCache)
|
||||
sc.m.Store(make(map[reflect.Type]*cStruct))
|
||||
|
||||
v := &Validate{
|
||||
tagName: config.TagName,
|
||||
fieldNameTag: config.FieldNameTag,
|
||||
tagCache: tc,
|
||||
structCache: sc,
|
||||
//read note 自定义一个错误池
|
||||
errsPool: &sync.Pool{New: func() interface{} {
|
||||
return ValidationErrors{}
|
||||
}}}
|
||||
|
||||
//read note 设置别名规则类(一组) 可参考: baked_in.go/bakedInAliasValidators
|
||||
if len(v.aliasValidators) == 0 {
|
||||
// must copy alias validators for separate validations to be used in each validator instance
|
||||
v.aliasValidators = map[string]string{}
|
||||
for k, val := range bakedInAliasValidators {
|
||||
//read note 默认别名校验器注册
|
||||
v.RegisterAliasValidation(k, val)
|
||||
}
|
||||
}
|
||||
|
||||
//read note 设置默认的校验方法.可参考: baked_in.go/bakedInValidators
|
||||
if len(v.validationFuncs) == 0 {
|
||||
// must copy validators for separate validations to be used in each instance
|
||||
v.validationFuncs = map[string]Func{}
|
||||
for k, val := range bakedInValidators {
|
||||
//read note 默认tag校验器注册
|
||||
v.RegisterValidation(k, val)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RegisterStructValidation registers a StructLevelFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) {
|
||||
v.initCheck()
|
||||
|
||||
if v.structLevelFuncs == nil {
|
||||
v.structLevelFuncs = map[reflect.Type]StructLevelFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
v.structLevelFuncs[reflect.TypeOf(t)] = fn
|
||||
fmt.Println(reflect.TypeOf(t))
|
||||
}
|
||||
|
||||
v.hasStructLevelFuncs = true
|
||||
}
|
||||
|
||||
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
|
||||
// NOTE: if the key already exists, the previous validation function will be replaced.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterValidation(key string, fn Func) error {
|
||||
v.initCheck()
|
||||
|
||||
if key == blank {
|
||||
return errors.New("Function Key cannot be empty")
|
||||
}
|
||||
|
||||
if fn == nil {
|
||||
return errors.New("Function cannot be empty")
|
||||
}
|
||||
|
||||
_, ok := restrictedTags[key]
|
||||
|
||||
if ok || strings.ContainsAny(key, restrictedTagChars) {
|
||||
panic(fmt.Sprintf(restrictedTagErr, key))
|
||||
}
|
||||
|
||||
v.validationFuncs[key] = fn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
//read note 注册类型的校验器,应该在校验之前就注册完成,因为该注册方法不是线程安全的.
|
||||
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
|
||||
v.initCheck()
|
||||
|
||||
if v.customTypeFuncs == nil {
|
||||
v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
v.customTypeFuncs[reflect.TypeOf(t)] = fn
|
||||
}
|
||||
|
||||
v.hasCustomFuncs = true
|
||||
}
|
||||
|
||||
// RegisterAliasValidation registers a mapping of a single validationstag that
|
||||
// defines a common or complex set of validation(s) to simplify adding validation
|
||||
// to structs. NOTE: when returning an error the tag returned in FieldError will be
|
||||
// the alias tag unless the dive tag is part of the alias; everything after the
|
||||
// dive tag is not reported as the alias tag. Also the ActualTag in the before case
|
||||
// will be the actual tag within the alias that failed.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
func (v *Validate) RegisterAliasValidation(alias, tags string) {
|
||||
v.initCheck()
|
||||
|
||||
_, ok := restrictedTags[alias]
|
||||
|
||||
if ok || strings.ContainsAny(alias, restrictedTagChars) {
|
||||
panic(fmt.Sprintf(restrictedAliasErr, alias))
|
||||
}
|
||||
|
||||
v.aliasValidators[alias] = tags
|
||||
v.hasAliasValidators = true
|
||||
}
|
||||
|
||||
// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
|
||||
// validate Array, Slice and maps fields which may contain more than one error
|
||||
//read note
|
||||
func (v *Validate) Field(field interface{}, tag string) error {
|
||||
v.initCheck()
|
||||
|
||||
if len(tag) == 0 || tag == skipValidationTag {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
fieldVal := reflect.ValueOf(field)
|
||||
|
||||
//read note 从tag缓存中获取 tag标签对应的校验方法
|
||||
ctag, ok := v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
//read note 加锁
|
||||
v.tagCache.lock.Lock()
|
||||
defer v.tagCache.lock.Unlock()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures tag
|
||||
// isn't parsed again.
|
||||
//read note 加锁之后在这边再获取一次,因为在上一次判断到加锁的过程中,可能有函数已经把对应方法加载进来了
|
||||
ctag, ok = v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
//read note
|
||||
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
|
||||
//read note 进行标签设值
|
||||
v.tagCache.Set(tag, ctag)
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
|
||||
|
||||
//read note 判断校验错误是否存在
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
|
||||
// validate Array, Slice and maps fields which may contain more than one error
|
||||
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
|
||||
v.initCheck()
|
||||
|
||||
if len(tag) == 0 || tag == skipValidationTag {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
topVal := reflect.ValueOf(val)
|
||||
|
||||
ctag, ok := v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
v.tagCache.lock.Lock()
|
||||
defer v.tagCache.lock.Unlock()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures tag
|
||||
// isn't parsed again.
|
||||
ctag, ok = v.tagCache.Get(tag)
|
||||
if !ok {
|
||||
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
|
||||
v.tagCache.Set(tag, ctag)
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructPartial validates the fields passed in only, ignoring all others.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) StructPartial(current interface{}, fields ...string) error {
|
||||
v.initCheck()
|
||||
|
||||
sv, _ := v.ExtractType(reflect.ValueOf(current))
|
||||
name := sv.Type().Name()
|
||||
m := map[string]struct{}{}
|
||||
|
||||
if fields != nil {
|
||||
for _, k := range fields {
|
||||
|
||||
flds := strings.Split(k, namespaceSeparator)
|
||||
if len(flds) > 0 {
|
||||
|
||||
key := name + namespaceSeparator
|
||||
for _, s := range flds {
|
||||
|
||||
idx := strings.Index(s, leftBracket)
|
||||
|
||||
if idx != -1 {
|
||||
for idx != -1 {
|
||||
key += s[:idx]
|
||||
m[key] = struct{}{}
|
||||
|
||||
idx2 := strings.Index(s, rightBracket)
|
||||
idx2++
|
||||
key += s[idx:idx2]
|
||||
m[key] = struct{}{}
|
||||
s = s[idx2:]
|
||||
idx = strings.Index(s, leftBracket)
|
||||
}
|
||||
} else {
|
||||
|
||||
key += s
|
||||
m[key] = struct{}{}
|
||||
}
|
||||
|
||||
key += namespaceSeparator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
|
||||
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// StructExcept validates all fields except the ones passed in.
|
||||
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) StructExcept(current interface{}, fields ...string) error {
|
||||
v.initCheck()
|
||||
|
||||
sv, _ := v.ExtractType(reflect.ValueOf(current))
|
||||
name := sv.Type().Name()
|
||||
m := map[string]struct{}{}
|
||||
|
||||
for _, key := range fields {
|
||||
m[name+namespaceSeparator+key] = struct{}{}
|
||||
}
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
|
||||
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false)
|
||||
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
|
||||
// it returns nil or ValidationErrors as error.
|
||||
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
|
||||
func (v *Validate) Struct(current interface{}) error {
|
||||
v.initCheck()
|
||||
|
||||
errs := v.errsPool.Get().(ValidationErrors)
|
||||
sv := reflect.ValueOf(current)
|
||||
|
||||
//read note 进行结构体校验
|
||||
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false)
|
||||
|
||||
//read note 校验之后,对校验错误进行处理.
|
||||
if len(errs) == 0 {
|
||||
v.errsPool.Put(errs)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, isStructOnly bool) {
|
||||
|
||||
//read note 指针处理成结构体Value(返回其指向的实际结构体)
|
||||
if current.Kind() == reflect.Ptr && !current.IsNil() {
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
//read note 结构体类型校验.
|
||||
if current.Kind() != reflect.Struct && current.Kind() != reflect.Interface {
|
||||
panic("value passed for validation is not a struct")
|
||||
}
|
||||
|
||||
//read note 校验结构体
|
||||
v.tranverseStruct(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, useStructName, partial, exclude, includeExclude, nil, nil)
|
||||
}
|
||||
|
||||
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
|
||||
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, ct *cTag) {
|
||||
|
||||
var ok bool
|
||||
first := len(nsPrefix) == 0
|
||||
typ := current.Type()
|
||||
|
||||
//read note 结构体校验器_缓存
|
||||
cs, ok = v.structCache.Get(typ)
|
||||
if !ok {
|
||||
cs = v.extractStructCache(current, typ.Name())
|
||||
}
|
||||
|
||||
if useStructName {
|
||||
errPrefix += cs.Name + namespaceSeparator
|
||||
|
||||
if len(v.fieldNameTag) != 0 {
|
||||
nsPrefix += cs.Name + namespaceSeparator
|
||||
}
|
||||
}
|
||||
|
||||
// structonly tag present don't tranverseFields
|
||||
// but must still check and run below struct level validation
|
||||
// if present
|
||||
//read note 【structonly】标签会忽略掉Field的校验
|
||||
if first || ct == nil || ct.typeof != typeStructOnly {
|
||||
|
||||
for _, f := range cs.fields {
|
||||
|
||||
if partial {
|
||||
|
||||
_, ok = includeExclude[errPrefix+f.Name]
|
||||
|
||||
if (ok && exclude) || (!ok && !exclude) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v.traverseField(topStruct, currentStruct, current.Field(f.Idx), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, f, f.cTags)
|
||||
}
|
||||
}
|
||||
|
||||
// check if any struct level validations, after all field validations already checked.
|
||||
//read note 如果结构体层的校验存在的话,需要进行调用.(这边是规则类校验)
|
||||
if cs.fn != nil {
|
||||
cs.fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
|
||||
}
|
||||
}
|
||||
|
||||
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
|
||||
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, cf *cField, ct *cTag) {
|
||||
|
||||
//read note 处理Ptr、Interface、Invalid(validator自定义)、自定义结构体类型.
|
||||
// 返回参数为:值 、 数据源类型、nullable(用来校验 【结构体】的【omiEmpty】标签...)
|
||||
current, kind, nullable := v.extractTypeInternal(current, false)
|
||||
var typ reflect.Type
|
||||
|
||||
switch kind {
|
||||
//read note 处理地址、Interface、Invalid是直接往校验错误池中添加一个Field错误?
|
||||
case reflect.Ptr, reflect.Interface, reflect.Invalid:
|
||||
|
||||
if ct == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ct.typeof == typeOmitEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
if ct.hasTag {
|
||||
|
||||
ns := errPrefix + cf.Name
|
||||
|
||||
//read note 如果是Invalid,需要往校验错误池中添加一个Field错误
|
||||
if kind == reflect.Invalid {
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.tag,
|
||||
Param: ct.param,
|
||||
Kind: kind,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.tag,
|
||||
Param: ct.param,
|
||||
Value: current.Interface(),
|
||||
Kind: kind,
|
||||
Type: current.Type(),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
typ = current.Type()
|
||||
|
||||
if typ != timeType {
|
||||
|
||||
//read note 对于结构体,这边有一个比较特殊的处理就是cTag是从第二个位置开始,也就是之前一直尝试structonly不成功,就是需要在structonly前面加一个tag才行。
|
||||
// 前一个对于struct应该是不生效才对,第二个才会对struct生效
|
||||
if ct != nil {
|
||||
ct = ct.next
|
||||
}
|
||||
|
||||
if ct != nil && ct.typeof == typeNoStructLevel {
|
||||
return
|
||||
}
|
||||
|
||||
//read note 如果上面的cTag没有生效/没有tagType为【typeNoStructLevel】的标签.就直接进入这个结构体
|
||||
v.tranverseStruct(topStruct, current, current, errPrefix+cf.Name+namespaceSeparator, nsPrefix+cf.AltName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cs, ct)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !ct.hasTag {
|
||||
return
|
||||
}
|
||||
|
||||
typ = current.Type()
|
||||
|
||||
//read note 进行字段Field的校验,这边的话是一个for的循环,在不同的标签中去找到对应的处理方式
|
||||
OUTER:
|
||||
for {
|
||||
if ct == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch ct.typeof {
|
||||
|
||||
case typeExists:
|
||||
ct = ct.next
|
||||
continue
|
||||
|
||||
case typeOmitEmpty:
|
||||
|
||||
if !nullable && !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
|
||||
return
|
||||
}
|
||||
|
||||
ct = ct.next
|
||||
continue
|
||||
|
||||
case typeDive:
|
||||
|
||||
ct = ct.next
|
||||
|
||||
// traverse slice or map here
|
||||
// or panic ;)
|
||||
switch kind {
|
||||
//read note 【dive】标签处理Slice 和 Array:往数组中的每一个元素进行处理
|
||||
case reflect.Slice, reflect.Array:
|
||||
|
||||
for i := 0; i < current.Len(); i++ {
|
||||
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct)
|
||||
}
|
||||
//read note 【dive】标签处理 Map:往Map中的每一个元素进行处理
|
||||
case reflect.Map:
|
||||
for _, key := range current.MapKeys() {
|
||||
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct)
|
||||
}
|
||||
//read note 【div】只能标注在 Slice 、 Map 、Array 上,不然就是报错.
|
||||
default:
|
||||
// throw error, if not a slice or map then should not have gotten here
|
||||
// bad dive tag
|
||||
panic("dive error! can't dive on a non slice or map")
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case typeOr:
|
||||
|
||||
//read note 【|】的处理
|
||||
errTag := blank
|
||||
|
||||
for {
|
||||
|
||||
//read note 如果结果是true的话,因为【|】标签只要有一个成立就可以,所以可以直接排除掉其他 【|】标签了
|
||||
if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
|
||||
|
||||
// drain rest of the 'or' values, then continue or leave
|
||||
//read note 排除其他 【|】标签,直到下一个Field的非【|】标签再进入处理.
|
||||
for {
|
||||
|
||||
ct = ct.next
|
||||
|
||||
if ct == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ct.typeof != typeOr {
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errTag += orSeparator + ct.tag
|
||||
|
||||
//read note 没有下一个cTag的处理器.处理一i西安错误信息,返回.
|
||||
if ct.next == nil {
|
||||
// if we get here, no valid 'or' value and no more tags
|
||||
|
||||
ns := errPrefix + cf.Name
|
||||
|
||||
if ct.hasAlias {
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.actualAliasTag,
|
||||
Value: current.Interface(),
|
||||
Type: typ,
|
||||
Kind: kind,
|
||||
}
|
||||
} else {
|
||||
errs[errPrefix+cf.Name] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: errTag[1:],
|
||||
ActualTag: errTag[1:],
|
||||
Value: current.Interface(),
|
||||
Type: typ,
|
||||
Kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ct = ct.next
|
||||
}
|
||||
|
||||
default:
|
||||
//read note 剩下的标签处理.类似一个循环的处理
|
||||
if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
|
||||
|
||||
ns := errPrefix + cf.Name
|
||||
|
||||
errs[ns] = &FieldError{
|
||||
FieldNamespace: ns,
|
||||
NameNamespace: nsPrefix + cf.AltName,
|
||||
Name: cf.AltName,
|
||||
Field: cf.Name,
|
||||
Tag: ct.aliasTag,
|
||||
ActualTag: ct.tag,
|
||||
Value: current.Interface(),
|
||||
Param: ct.param,
|
||||
Type: typ,
|
||||
Kind: kind,
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
ct = ct.next
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/11 16:16
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testValidator
|
||||
|
||||
type Ower struct {
|
||||
Age int `validate:"min=10"`
|
||||
}
|
||||
|
||||
type Cat struct {
|
||||
Age int `validate:"min=10"`
|
||||
Ower Ower
|
||||
}
|
||||
|
||||
type Man struct {
|
||||
Name string `validate:"ipe"`
|
||||
}
|
||||
|
||||
type Woman struct {
|
||||
Name string `validate:"diy=5"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
M map[string]int `validate:"min=10"`
|
||||
H int `validate:"max=20"`
|
||||
Exist bool `validate:"exists"`
|
||||
Require bool `validate:"required"`
|
||||
Cat Cat
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* @Author : huangzj
|
||||
* @Time : 2020/12/11 16:16
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
package testValidator
|
||||
|
||||
import (
|
||||
"Go-Tool/SourceAnalysisAndTool/validator.v8/sourceAnalysis/gopkg.in/go-playground/validator.v8"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidator(t *testing.T) {
|
||||
fmt.Println("测试别名校验器")
|
||||
testAlias()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("测试默认和自定义tag校验器")
|
||||
testTag()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("测试字段校验器")
|
||||
testField()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("测试结构体校验器")
|
||||
testStruct()
|
||||
|
||||
}
|
||||
|
||||
func testStruct() {
|
||||
//添加结构体校验,对结构体进行处理之后,validator的标签还是会起作用
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
valid.RegisterStructValidation(func(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||
c := structLevel.CurrentStruct.Interface().(Cat)
|
||||
if c.Age < 100 {
|
||||
fmt.Println("猫的岁数不能小于100")
|
||||
}
|
||||
}, Cat{})
|
||||
p := Person{
|
||||
Name: "192.168.0.1",
|
||||
M: map[string]int{"A": 1},
|
||||
H: 2000,
|
||||
Exist: false,
|
||||
Require: false,
|
||||
Cat: Cat{
|
||||
Age: 0,
|
||||
Ower: Ower{},
|
||||
},
|
||||
}
|
||||
|
||||
err := valid.Struct(p)
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
func testField() {
|
||||
//如果注册了对应的Field,则会调用自定义的方法,不会再使用validator定义的标签
|
||||
//这边测试了结构体和基本类型,都是如上述描述的这么处理
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
valid.RegisterAliasValidation("ipe", "ip|ipv4|ipv6")
|
||||
|
||||
valid.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
|
||||
d := field.Interface().(Cat)
|
||||
if d.Age < 50 {
|
||||
fmt.Println("狗的岁数不能小于50")
|
||||
}
|
||||
return field
|
||||
}, Cat{})
|
||||
|
||||
p := Person{
|
||||
Name: "192.168.0.1",
|
||||
M: map[string]int{"A": 1},
|
||||
H: 2000,
|
||||
Exist: false,
|
||||
Require: false,
|
||||
Cat: Cat{
|
||||
Age: 0,
|
||||
Ower: Ower{},
|
||||
},
|
||||
}
|
||||
err := valid.Struct(p)
|
||||
fmt.Println(err)
|
||||
|
||||
valid.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
|
||||
d := field.Interface().(int)
|
||||
if d != 10 {
|
||||
fmt.Println("测试一下")
|
||||
}
|
||||
return field
|
||||
}, 1)
|
||||
|
||||
err1 := valid.Struct(p)
|
||||
fmt.Println(err1)
|
||||
|
||||
}
|
||||
|
||||
func testTag() {
|
||||
w := Woman{
|
||||
Name: "my name is Woman",
|
||||
}
|
||||
|
||||
w1 := Woman{
|
||||
Name: "my name is wo-man",
|
||||
}
|
||||
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
_ = valid.RegisterValidation("diy", func(v *validator.Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool {
|
||||
s := field.Interface().(string)
|
||||
|
||||
i, _ := strconv.ParseInt(param, 0, 64)
|
||||
if int64(len(s)) < i || strings.Contains(s, "Woman") {
|
||||
fmt.Println("校验一下")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
err := valid.Struct(w)
|
||||
fmt.Println(err)
|
||||
|
||||
err1 := valid.Struct(w1)
|
||||
fmt.Println(err1)
|
||||
}
|
||||
|
||||
func testAlias() {
|
||||
m := Man{
|
||||
Name: "192.168.0.1",
|
||||
}
|
||||
|
||||
m1 := Man{
|
||||
Name: "213123123",
|
||||
}
|
||||
|
||||
config := &validator.Config{
|
||||
TagName: "validate",
|
||||
}
|
||||
valid := validator.New(config)
|
||||
valid.RegisterAliasValidation("ipe", "ip|ipv4|ipv6")
|
||||
err := valid.Struct(m)
|
||||
fmt.Println(err)
|
||||
|
||||
err1 := valid.Struct(m1)
|
||||
fmt.Println(err1)
|
||||
}
|
||||
@@ -4,16 +4,18 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75 // indirect
|
||||
github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible // indirect
|
||||
github.com/ahmetb/go-linq v2.0.0-rc0+incompatible
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect
|
||||
github.com/go-ini/ini v1.57.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/kardianos/govendor v1.0.9 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/stretchr/testify v1.5.1 // indirect
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect
|
||||
golang.org/x/tools v0.0.0-20200429213335-127c98bd7927 // indirect
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75 h1:xGHheKK44eC6K0u5X
|
||||
github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ=
|
||||
github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible h1:8q+6K5jJ2RCTD7v7VxMfuUdHyU22GZi+qMY0hpLTnVc=
|
||||
github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible/go.mod h1:IBvGwXyZVvXuN8KIbGCK53k+fzCKOmAdYhobMdJXOck=
|
||||
github.com/ahmetb/go-linq v2.0.0-rc0+incompatible h1:H8yA1M8T+1/pyPRUwSOyc30MFrtiP9vgJ2s+lgdaQlM=
|
||||
github.com/ahmetb/go-linq v2.0.0-rc0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -10,16 +12,24 @@ github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3N
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU=
|
||||
github.com/go-ini/ini v1.57.0 h1:Qwzj3wZQW+Plax5Ntj+GYe07DfGj1OH+aL1nMTMaNow=
|
||||
github.com/go-ini/ini v1.57.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc=
|
||||
github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -45,6 +55,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
|
||||
2020/12/7 :添加container包使用示例和源码分析
|
||||
|
||||
2020/12/14:添加validator.v8源码解析和使用示例
|
||||
|
||||
# mod vendor模式加载包
|
||||
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到
|
||||
|
||||
|
||||
+1
@@ -2,6 +2,7 @@ sudo: false
|
||||
|
||||
language: go
|
||||
go:
|
||||
- 1.5
|
||||
- 1.7
|
||||
|
||||
before_install:
|
||||
|
||||
+10
-197
@@ -1,201 +1,14 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
Copyright 2016 Ahmet Alp Balkan
|
||||
|
||||
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 2016 Ahmet Alp Balkan
|
||||
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
+10
-91
@@ -1,21 +1,16 @@
|
||||
# go-linq [](https://godoc.org/github.com/ahmetalpbalkan/go-linq) [](https://travis-ci.org/ahmetalpbalkan/go-linq) [](https://coveralls.io/github/ahmetalpbalkan/go-linq?branch=master) [](https://goreportcard.com/report/github.com/ahmetalpbalkan/go-linq)
|
||||
A powerful language integrated query (LINQ) library for Go.
|
||||
* Written in vanilla Go, no dependencies!
|
||||
* Complete lazy evaluation with iterator pattern
|
||||
* Written in vanilla Go!
|
||||
* Safe for concurrent use
|
||||
* Supports generic functions to make your code cleaner and free of type assertions
|
||||
* Complete lazy evaluation with iterator pattern
|
||||
* Supports arrays, slices, maps, strings, channels and custom collections
|
||||
(collection needs to implement `Iterable` interface and element - `Comparable`
|
||||
interface)
|
||||
|
||||
## Installation
|
||||
|
||||
$ go get github.com/ahmetalpbalkan/go-linq
|
||||
|
||||
We recommend using a dependency manager (e.g. [govendor][govendor] or
|
||||
[godep][godep]) to maintain a local copy of this package in your project.
|
||||
|
||||
[govendor]: https://github.com/kardianos/govendor
|
||||
[godep]: https://github.com/tools/godep/
|
||||
|
||||
> :warning: :warning: `go-linq` has recently introduced _breaking API changes_
|
||||
> with v2.0.0. See [release notes](#release-notes) for details. v2.0.0 comes with
|
||||
> a refined interface, dramatically increased performance and memory efficiency,
|
||||
@@ -31,20 +26,16 @@ Usage is as easy as chaining methods like:
|
||||
|
||||
`From(slice)` `.Where(predicate)` `.Select(selector)` `.Union(data)`
|
||||
|
||||
**Example 1: Find all owners of cars manufactured after 2015**
|
||||
|
||||
**Example: Find all owners of cars manufactured from 2015**
|
||||
```go
|
||||
import . "github.com/ahmetalpbalkan/go-linq"
|
||||
|
||||
type Car struct {
|
||||
year int
|
||||
id, year int
|
||||
owner, model string
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
|
||||
var owners []string
|
||||
owners := []string{}
|
||||
|
||||
From(cars).Where(func(c interface{}) bool {
|
||||
return c.(Car).year >= 2015
|
||||
@@ -53,21 +44,7 @@ From(cars).Where(func(c interface{}) bool {
|
||||
}).ToSlice(&owners)
|
||||
```
|
||||
|
||||
Or, you can use generic functions, like `WhereT` and `SelectT` to simplify your code
|
||||
(at a performance penalty):
|
||||
|
||||
```go
|
||||
var owners []string
|
||||
|
||||
From(cars).WhereT(func(c Car) bool {
|
||||
return c.year >= 2015
|
||||
}).SelectT(func(c Car) string {
|
||||
return c.owner
|
||||
}).ToSlice(&owners)
|
||||
```
|
||||
|
||||
**Example 2: Find the author who has written the most books**
|
||||
|
||||
**Example: Find the author who has written the most books**
|
||||
```go
|
||||
import . "github.com/ahmetalpbalkan/go-linq"
|
||||
|
||||
@@ -94,8 +71,7 @@ author := From(books).SelectMany( // make a flat array of authors
|
||||
}).First() // take the first author
|
||||
```
|
||||
|
||||
**Example 3: Implement a custom method that leaves only values greater than the specified threshold**
|
||||
|
||||
**Example: Implement a custom method that leaves only values greater than the specified threshold**
|
||||
```go
|
||||
type MyQuery Query
|
||||
|
||||
@@ -120,67 +96,10 @@ func (q MyQuery) GreaterThan(threshold int) Query {
|
||||
result := MyQuery(Range(1,10)).GreaterThan(5).Results()
|
||||
```
|
||||
|
||||
## Generic Functions
|
||||
|
||||
Although Go doesn't implement generics, with some reflection tricks, you can use go-linq without
|
||||
typing `interface{}`s and type assertions. This will introduce a performance penalty (5x-10x slower)
|
||||
but will yield in a cleaner and more readable code.
|
||||
|
||||
Methods with `T` suffix (such as `WhereT`) accept functions with generic types. So instead of
|
||||
|
||||
.Select(func(v interface{}) interface{} {...})
|
||||
|
||||
you can type:
|
||||
|
||||
.SelectT(func(v YourType) YourOtherType {...})
|
||||
|
||||
This will make your code free of `interface{}` and type assertions.
|
||||
|
||||
**Example 4: "MapReduce" in a slice of string sentences to list the top 5 most used words using generic functions**
|
||||
|
||||
```go
|
||||
var results []string
|
||||
|
||||
From(sentences).
|
||||
// split sentences to words
|
||||
SelectManyT(func(sentence string) Query {
|
||||
return From(strings.Split(sentence, " "))
|
||||
}).
|
||||
// group the words
|
||||
GroupByT(
|
||||
func(word string) string { return word },
|
||||
func(word string) string { return word },
|
||||
).
|
||||
// order by count
|
||||
OrderByDescendingT(func(wordGroup Group) int {
|
||||
return len(wordGroup.Group)
|
||||
}).
|
||||
// order by the word
|
||||
ThenByT(func(wordGroup Group) string {
|
||||
return wordGroup.Key.(string)
|
||||
}).
|
||||
Take(5). // take the top 5
|
||||
// project the words using the index as rank
|
||||
SelectIndexedT(func(index int, wordGroup Group) string {
|
||||
return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group))
|
||||
}).
|
||||
ToSlice(&results)
|
||||
```
|
||||
|
||||
**More examples** can be found in the [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq).
|
||||
**More examples** can be found in [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq).
|
||||
|
||||
## Release Notes
|
||||
|
||||
~~~
|
||||
v3.0.0 (2017-01-10)
|
||||
* Breaking change: ToSlice() now overwrites existing slice starting
|
||||
from index 0 and grows/reslices it as needed.
|
||||
* Generic methods support (thanks @cleitonmarx!)
|
||||
- Accepting parametrized functions was originally proposed in #26
|
||||
- You can now avoid type assertions and interface{}s
|
||||
- Functions with generic methods are named as "MethodNameT" and
|
||||
signature for the existing LINQ methods are unchanged.
|
||||
* Added ForEach(), ForEachIndexed() and AggregateWithSeedBy().
|
||||
|
||||
v2.0.0 (2016-09-02)
|
||||
* IMPORTANT: This release is a BREAKING CHANGE. The old version
|
||||
|
||||
+19
-124
@@ -2,15 +2,16 @@ package linq
|
||||
|
||||
// Aggregate applies an accumulator function over a sequence.
|
||||
//
|
||||
// Aggregate method makes it simple to perform a calculation over a sequence of
|
||||
// values. This method works by calling f() one time for each element in source
|
||||
// except the first one. Each time f() is called, Aggregate passes both the
|
||||
// element from the sequence and an aggregated value (as the first argument to
|
||||
// f()). The first element of source is used as the initial aggregate value. The
|
||||
// result of f() replaces the previous aggregated value.
|
||||
// Aggregate method makes it simple to perform a calculation over a sequence of values.
|
||||
// This method works by calling f() one time for each element in source
|
||||
// except the first one. Each time f() is called, Aggregate passes both
|
||||
// the element from the sequence and an aggregated value (as the first argument to f()).
|
||||
// The first element of source is used as the initial aggregate value.
|
||||
// The result of f() replaces the previous aggregated value.
|
||||
//
|
||||
// Aggregate returns the final result of f().
|
||||
func (q Query) Aggregate(f func(interface{}, interface{}) interface{}) interface{} {
|
||||
func (q Query) Aggregate(
|
||||
f func(interface{}, interface{}) interface{}) interface{} {
|
||||
next := q.Iterate()
|
||||
|
||||
result, any := next()
|
||||
@@ -25,40 +26,21 @@ func (q Query) Aggregate(f func(interface{}, interface{}) interface{}) interface
|
||||
return result
|
||||
}
|
||||
|
||||
// AggregateT is the typed version of Aggregate.
|
||||
// AggregateWithSeed applies an accumulator function over a sequence.
|
||||
// The specified seed value is used as the initial accumulator value.
|
||||
//
|
||||
// - f is of type: func(TSource, TSource) TSource
|
||||
//
|
||||
// NOTE: Aggregate has better performance than AggregateT.
|
||||
func (q Query) AggregateT(f interface{}) interface{} {
|
||||
fGenericFunc, err := newGenericFunc(
|
||||
"AggregateT", "f", f,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fFunc := func(result interface{}, current interface{}) interface{} {
|
||||
return fGenericFunc.Call(result, current)
|
||||
}
|
||||
|
||||
return q.Aggregate(fFunc)
|
||||
}
|
||||
|
||||
// AggregateWithSeed applies an accumulator function over a sequence. The
|
||||
// specified seed value is used as the initial accumulator value.
|
||||
//
|
||||
// Aggregate method makes it simple to perform a calculation over a sequence of
|
||||
// values. This method works by calling f() one time for each element in source
|
||||
// except the first one. Each time f() is called, Aggregate passes both the
|
||||
// element from the sequence and an aggregated value (as the first argument to
|
||||
// f()). The value of the seed parameter is used as the initial aggregate value.
|
||||
// Aggregate method makes it simple to perform a calculation over a sequence of values.
|
||||
// This method works by calling f() one time for each element in source
|
||||
// except the first one. Each time f() is called, Aggregate passes both
|
||||
// the element from the sequence and an aggregated value (as the first argument to f()).
|
||||
// The value of the seed parameter is used as the initial aggregate value.
|
||||
// The result of f() replaces the previous aggregated value.
|
||||
//
|
||||
// Aggregate returns the final result of f().
|
||||
func (q Query) AggregateWithSeed(seed interface{},
|
||||
f func(interface{}, interface{}) interface{}) interface{} {
|
||||
func (q Query) AggregateWithSeed(
|
||||
seed interface{},
|
||||
f func(interface{}, interface{}) interface{},
|
||||
) interface{} {
|
||||
|
||||
next := q.Iterate()
|
||||
result := seed
|
||||
@@ -69,90 +51,3 @@ func (q Query) AggregateWithSeed(seed interface{},
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AggregateWithSeedT is the typed version of AggregateWithSeed.
|
||||
//
|
||||
// - f is of type "func(TAccumulate, TSource) TAccumulate"
|
||||
//
|
||||
// NOTE: AggregateWithSeed has better performance than
|
||||
// AggregateWithSeedT.
|
||||
func (q Query) AggregateWithSeedT(seed interface{},
|
||||
f interface{}) interface{} {
|
||||
fGenericFunc, err := newGenericFunc(
|
||||
"AggregateWithSeed", "f", f,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fFunc := func(result interface{}, current interface{}) interface{} {
|
||||
return fGenericFunc.Call(result, current)
|
||||
}
|
||||
|
||||
return q.AggregateWithSeed(seed, fFunc)
|
||||
}
|
||||
|
||||
// AggregateWithSeedBy applies an accumulator function over a sequence. The
|
||||
// specified seed value is used as the initial accumulator value, and the
|
||||
// specified function is used to select the result value.
|
||||
//
|
||||
// Aggregate method makes it simple to perform a calculation over a sequence of
|
||||
// values. This method works by calling f() one time for each element in source.
|
||||
// Each time func is called, Aggregate passes both the element from the sequence
|
||||
// and an aggregated value (as the first argument to func). The value of the
|
||||
// seed parameter is used as the initial aggregate value. The result of func
|
||||
// replaces the previous aggregated value.
|
||||
//
|
||||
// The final result of func is passed to resultSelector to obtain the final
|
||||
// result of Aggregate.
|
||||
func (q Query) AggregateWithSeedBy(seed interface{},
|
||||
f func(interface{}, interface{}) interface{},
|
||||
resultSelector func(interface{}) interface{}) interface{} {
|
||||
|
||||
next := q.Iterate()
|
||||
result := seed
|
||||
|
||||
for current, ok := next(); ok; current, ok = next() {
|
||||
result = f(result, current)
|
||||
}
|
||||
|
||||
return resultSelector(result)
|
||||
}
|
||||
|
||||
// AggregateWithSeedByT is the typed version of AggregateWithSeedBy.
|
||||
//
|
||||
// - f is of type "func(TAccumulate, TSource) TAccumulate"
|
||||
// - resultSelectorFn is of type "func(TAccumulate) TResult"
|
||||
//
|
||||
// NOTE: AggregateWithSeedBy has better performance than
|
||||
// AggregateWithSeedByT.
|
||||
func (q Query) AggregateWithSeedByT(seed interface{},
|
||||
f interface{},
|
||||
resultSelectorFn interface{}) interface{} {
|
||||
fGenericFunc, err := newGenericFunc(
|
||||
"AggregateWithSeedByT", "f", f,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fFunc := func(result interface{}, current interface{}) interface{} {
|
||||
return fGenericFunc.Call(result, current)
|
||||
}
|
||||
|
||||
resultSelectorGenericFunc, err := newGenericFunc(
|
||||
"AggregateWithSeedByT", "resultSelectorFn", resultSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultSelectorFunc := func(result interface{}) interface{} {
|
||||
return resultSelectorGenericFunc.Call(result)
|
||||
}
|
||||
|
||||
return q.AggregateWithSeedBy(seed, fFunc, resultSelectorFunc)
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ package linq
|
||||
|
||||
type comparer func(interface{}, interface{}) int
|
||||
|
||||
// Comparable is an interface that has to be implemented by a custom collection
|
||||
// elememts in order to work with linq.
|
||||
// Comparable is an interface that has to be implemented by a
|
||||
// custom collection elememts in order to work with linq.
|
||||
//
|
||||
// Example:
|
||||
// func (f foo) CompareTo(c Comparable) int {
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
package linq
|
||||
|
||||
// Append inserts an item to the end of a collection, so it becomes the last
|
||||
// item.
|
||||
// Append inserts an item to the end of a collection,
|
||||
// so it becomes the last item.
|
||||
func (q Query) Append(item interface{}) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -28,8 +28,8 @@ func (q Query) Append(item interface{}) Query {
|
||||
// Concat concatenates two collections.
|
||||
//
|
||||
// The Concat method differs from the Union method because the Concat method
|
||||
// returns all the original elements in the input sequences. The Union method
|
||||
// returns only unique elements.
|
||||
// returns all the original elements in the input sequences.
|
||||
// The Union method returns only unique elements.
|
||||
func (q Query) Concat(q2 Query) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -53,8 +53,8 @@ func (q Query) Concat(q2 Query) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// Prepend inserts an item to the beginning of a collection, so it becomes the
|
||||
// first item.
|
||||
// Prepend inserts an item to the beginning of a collection,
|
||||
// so it becomes the first item.
|
||||
func (q Query) Prepend(item interface{}) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
|
||||
+5
-28
@@ -1,7 +1,7 @@
|
||||
package linq
|
||||
|
||||
// Distinct method returns distinct elements from a collection. The result is an
|
||||
// unordered collection that contains no duplicate values.
|
||||
// Distinct method returns distinct elements from a collection.
|
||||
// The result is an unordered collection that contains no duplicate values.
|
||||
func (q Query) Distinct() Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -22,11 +22,11 @@ func (q Query) Distinct() Query {
|
||||
}
|
||||
}
|
||||
|
||||
// Distinct method returns distinct elements from a collection. The result is an
|
||||
// ordered collection that contains no duplicate values.
|
||||
// Distinct method returns distinct elements from a collection.
|
||||
// The result is an ordered collection that contains no duplicate values.
|
||||
//
|
||||
// NOTE: Distinct method on OrderedQuery type has better performance than
|
||||
// Distinct method on Query type.
|
||||
// Distinct method on Query type
|
||||
func (oq OrderedQuery) Distinct() OrderedQuery {
|
||||
return OrderedQuery{
|
||||
orders: oq.orders,
|
||||
@@ -73,26 +73,3 @@ func (q Query) DistinctBy(selector func(interface{}) interface{}) Query {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DistinctByT is the typed version of DistinctBy.
|
||||
//
|
||||
// - selectorFn is of type "func(TSource) TSource".
|
||||
//
|
||||
// NOTE: DistinctBy has better performance than DistinctByT.
|
||||
func (q Query) DistinctByT(selectorFn interface{}) Query {
|
||||
selectorFunc, ok := selectorFn.(func(interface{}) interface{})
|
||||
if !ok {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"DistinctByT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc = func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
}
|
||||
return q.DistinctBy(selectorFunc)
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
// Package linq provides methods for querying and manipulating slices, arrays,
|
||||
// maps, strings, channels and collections.
|
||||
// Package linq provides methods for querying and manipulating
|
||||
// slices, arrays, maps, strings, channels and collections.
|
||||
//
|
||||
// Authors: Alexander Kalankhodzhaev (kalan), Ahmet Alp Balkan, Cleiton Marques
|
||||
// Souza.
|
||||
// Authors: Alexander Kalankhodzhaev (kalan), Ahmet Alp Balkan
|
||||
package linq
|
||||
|
||||
+9
-29
@@ -1,7 +1,8 @@
|
||||
package linq
|
||||
|
||||
// Except produces the set difference of two sequences. The set difference is
|
||||
// the members of the first sequence that don't appear in the second sequence.
|
||||
// Except produces the set difference of two sequences.
|
||||
// The set difference is the members of the first sequence
|
||||
// that don't appear in the second sequence.
|
||||
func (q Query) Except(q2 Query) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -26,11 +27,12 @@ func (q Query) Except(q2 Query) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// ExceptBy invokes a transform function on each element of a collection and
|
||||
// produces the set difference of two sequences. The set difference is the
|
||||
// members of the first sequence that don't appear in the second sequence.
|
||||
func (q Query) ExceptBy(q2 Query,
|
||||
selector func(interface{}) interface{}) Query {
|
||||
// ExceptBy invokes a transform function on each element of a collection
|
||||
// and produces the set difference of two sequences.
|
||||
// The set difference is the members of the first sequence
|
||||
// that don't appear in the second sequence.
|
||||
func (q Query) ExceptBy(
|
||||
q2 Query, selector func(interface{}) interface{}) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
next := q.Iterate()
|
||||
@@ -55,25 +57,3 @@ func (q Query) ExceptBy(q2 Query,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ExceptByT is the typed version of ExceptBy.
|
||||
//
|
||||
// - selectorFn is of type "func(TSource) TSource"
|
||||
//
|
||||
// NOTE: ExceptBy has better performance than ExceptByT.
|
||||
func (q Query) ExceptByT(q2 Query,
|
||||
selectorFn interface{}) Query {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"ExceptByT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
return q.ExceptBy(q2, selectorFunc)
|
||||
}
|
||||
|
||||
+17
-17
@@ -5,30 +5,30 @@ import "reflect"
|
||||
// Iterator is an alias for function to iterate over data.
|
||||
type Iterator func() (item interface{}, ok bool)
|
||||
|
||||
// Query is the type returned from query functions. It can be iterated manually
|
||||
// as shown in the example.
|
||||
// Query is the type returned from query functions.
|
||||
// It can be iterated manually as shown in the example.
|
||||
type Query struct {
|
||||
Iterate func() Iterator
|
||||
}
|
||||
|
||||
// KeyValue is a type that is used to iterate over a map (if query is created
|
||||
// from a map). This type is also used by ToMap() method to output result of a
|
||||
// query into a map.
|
||||
// KeyValue is a type that is used to iterate over a map
|
||||
// (if query is created from a map). This type is also used by
|
||||
// ToMap() method to output result of a query into a map.
|
||||
type KeyValue struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Iterable is an interface that has to be implemented by a custom collection in
|
||||
// order to work with linq.
|
||||
// Iterable is an interface that has to be implemented by a
|
||||
// custom collection in order to work with linq.
|
||||
type Iterable interface {
|
||||
Iterate() Iterator
|
||||
}
|
||||
|
||||
// From initializes a linq query with passed slice, array or map as the source.
|
||||
// String, channel or struct implementing Iterable interface can be used as an
|
||||
// input. In this case From delegates it to FromString, FromChannel and
|
||||
// FromIterable internally.
|
||||
// From initializes a linq query with passed slice, array or map
|
||||
// as the source. String, channel or struct implementing Iterable
|
||||
// interface can be used as an input. In this case From delegates it
|
||||
// to FromString, FromChannel and FromIterable internally.
|
||||
func From(source interface{}) Query {
|
||||
src := reflect.ValueOf(source)
|
||||
|
||||
@@ -84,8 +84,8 @@ func From(source interface{}) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// FromChannel initializes a linq query with passed channel, linq iterates over
|
||||
// channel until it is closed.
|
||||
// FromChannel initializes a linq query with passed channel,
|
||||
// linq iterates over channel until it is closed.
|
||||
func FromChannel(source <-chan interface{}) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -97,8 +97,8 @@ func FromChannel(source <-chan interface{}) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// FromString initializes a linq query with passed string, linq iterates over
|
||||
// runes of string.
|
||||
// FromString initializes a linq query with passed string,
|
||||
// linq iterates over runes of string.
|
||||
func FromString(source string) Query {
|
||||
runes := []rune(source)
|
||||
len := len(runes)
|
||||
@@ -120,8 +120,8 @@ func FromString(source string) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// FromIterable initializes a linq query with custom collection passed. This
|
||||
// collection has to implement Iterable interface, linq iterates over items,
|
||||
// FromIterable initializes a linq query with custom collection passed.
|
||||
// This collection has to implement Iterable interface, linq iterates over items,
|
||||
// that has to implement Comparable interface or be basic types.
|
||||
func FromIterable(source Iterable) Query {
|
||||
return Query{
|
||||
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
package linq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// genericType represents a any reflect.Type.
|
||||
type genericType int
|
||||
|
||||
var genericTp = reflect.TypeOf(new(genericType)).Elem()
|
||||
|
||||
// functionCache keeps genericFunc reflection objects in cache.
|
||||
type functionCache struct {
|
||||
MethodName string
|
||||
ParamName string
|
||||
FnValue reflect.Value
|
||||
FnType reflect.Type
|
||||
TypesIn []reflect.Type
|
||||
TypesOut []reflect.Type
|
||||
}
|
||||
|
||||
// genericFunc is a type used to validate and call dynamic functions.
|
||||
type genericFunc struct {
|
||||
Cache *functionCache
|
||||
}
|
||||
|
||||
// Call calls a dynamic function.
|
||||
func (g *genericFunc) Call(params ...interface{}) interface{} {
|
||||
paramsIn := make([]reflect.Value, len(params))
|
||||
for i, param := range params {
|
||||
paramsIn[i] = reflect.ValueOf(param)
|
||||
}
|
||||
paramsOut := g.Cache.FnValue.Call(paramsIn)
|
||||
if len(paramsOut) >= 1 {
|
||||
return paramsOut[0].Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newGenericFunc instantiates a new genericFunc pointer
|
||||
func newGenericFunc(methodName, paramName string, fn interface{}, validateFunc func(*functionCache) error) (*genericFunc, error) {
|
||||
cache := &functionCache{}
|
||||
cache.FnValue = reflect.ValueOf(fn)
|
||||
|
||||
if cache.FnValue.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("%s: parameter [%s] is not a function type. It is a '%s'", methodName, paramName, cache.FnValue.Type())
|
||||
}
|
||||
cache.MethodName = methodName
|
||||
cache.ParamName = paramName
|
||||
cache.FnType = cache.FnValue.Type()
|
||||
numTypesIn := cache.FnType.NumIn()
|
||||
cache.TypesIn = make([]reflect.Type, numTypesIn)
|
||||
for i := 0; i < numTypesIn; i++ {
|
||||
cache.TypesIn[i] = cache.FnType.In(i)
|
||||
}
|
||||
|
||||
numTypesOut := cache.FnType.NumOut()
|
||||
cache.TypesOut = make([]reflect.Type, numTypesOut)
|
||||
for i := 0; i < numTypesOut; i++ {
|
||||
cache.TypesOut[i] = cache.FnType.Out(i)
|
||||
}
|
||||
if err := validateFunc(cache); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &genericFunc{Cache: cache}, nil
|
||||
}
|
||||
|
||||
// simpleParamValidator creates a function to validate genericFunc based in the
|
||||
// In and Out function parameters.
|
||||
func simpleParamValidator(In []reflect.Type, Out []reflect.Type) func(cache *functionCache) error {
|
||||
return func(cache *functionCache) error {
|
||||
var isValid = func() bool {
|
||||
if In != nil {
|
||||
if len(In) != len(cache.TypesIn) {
|
||||
return false
|
||||
}
|
||||
for i, paramIn := range In {
|
||||
if paramIn != genericTp && paramIn != cache.TypesIn[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if Out != nil {
|
||||
if len(Out) != len(cache.TypesOut) {
|
||||
return false
|
||||
}
|
||||
for i, paramOut := range Out {
|
||||
if paramOut != genericTp && paramOut != cache.TypesOut[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !isValid() {
|
||||
return fmt.Errorf("%s: parameter [%s] has a invalid function signature. Expected: '%s', actual: '%s'", cache.MethodName, cache.ParamName, formatFnSignature(In, Out), formatFnSignature(cache.TypesIn, cache.TypesOut))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// newElemTypeSlice creates a slice of items elem types.
|
||||
func newElemTypeSlice(items ...interface{}) []reflect.Type {
|
||||
typeList := make([]reflect.Type, len(items))
|
||||
for i, item := range items {
|
||||
typeItem := reflect.TypeOf(item)
|
||||
if typeItem.Kind() == reflect.Ptr {
|
||||
typeList[i] = typeItem.Elem()
|
||||
}
|
||||
}
|
||||
return typeList
|
||||
}
|
||||
|
||||
// formatFnSignature formats the func signature based in the parameters types.
|
||||
func formatFnSignature(In []reflect.Type, Out []reflect.Type) string {
|
||||
paramInNames := make([]string, len(In))
|
||||
for i, typeIn := range In {
|
||||
if typeIn == genericTp {
|
||||
paramInNames[i] = "T"
|
||||
} else {
|
||||
paramInNames[i] = typeIn.String()
|
||||
}
|
||||
|
||||
}
|
||||
paramOutNames := make([]string, len(Out))
|
||||
for i, typeOut := range Out {
|
||||
if typeOut == genericTp {
|
||||
paramOutNames[i] = "T"
|
||||
} else {
|
||||
paramOutNames[i] = typeOut.String()
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("func(%s)%s", strings.Join(paramInNames, ","), strings.Join(paramOutNames, ","))
|
||||
}
|
||||
+8
-41
@@ -6,11 +6,14 @@ type Group struct {
|
||||
Group []interface{}
|
||||
}
|
||||
|
||||
// GroupBy method groups the elements of a collection according to a specified
|
||||
// key selector function and projects the elements for each group by using a
|
||||
// specified function.
|
||||
func (q Query) GroupBy(keySelector func(interface{}) interface{},
|
||||
elementSelector func(interface{}) interface{}) Query {
|
||||
// GroupBy method groups the elements of a collection according
|
||||
// to a specified key selector function and projects the elements for each group
|
||||
// by using a specified function.
|
||||
func (q Query) GroupBy(
|
||||
keySelector func(interface{}) interface{},
|
||||
elementSelector func(interface{}) interface{},
|
||||
) Query {
|
||||
|
||||
return Query{
|
||||
func() Iterator {
|
||||
next := q.Iterate()
|
||||
@@ -43,39 +46,3 @@ func (q Query) GroupBy(keySelector func(interface{}) interface{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GroupByT is the typed version of GroupBy.
|
||||
//
|
||||
// - keySelectorFn is of type "func(TSource) TKey"
|
||||
// - elementSelectorFn is of type "func(TSource) TElement"
|
||||
//
|
||||
// NOTE: GroupBy has better performance than GroupByT.
|
||||
func (q Query) GroupByT(keySelectorFn interface{},
|
||||
elementSelectorFn interface{}) Query {
|
||||
keySelectorGenericFunc, err := newGenericFunc(
|
||||
"GroupByT", "keySelectorFn", keySelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keySelectorFunc := func(item interface{}) interface{} {
|
||||
return keySelectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
elementSelectorGenericFunc, err := newGenericFunc(
|
||||
"GroupByT", "elementSelectorFn", elementSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
elementSelectorFunc := func(item interface{}) interface{} {
|
||||
return elementSelectorGenericFunc.Call(item)
|
||||
|
||||
}
|
||||
|
||||
return q.GroupBy(keySelectorFunc, elementSelectorFunc)
|
||||
}
|
||||
|
||||
+12
-69
@@ -1,27 +1,25 @@
|
||||
package linq
|
||||
|
||||
import "reflect"
|
||||
|
||||
// GroupJoin correlates the elements of two collections based on key equality,
|
||||
// and groups the results.
|
||||
//
|
||||
// This method produces hierarchical results, which means that elements from
|
||||
// outer query are paired with collections of matching elements from inner.
|
||||
// GroupJoin enables you to base your results on a whole set of matches for each
|
||||
// element of outer query.
|
||||
// This method produces hierarchical results, which means that elements from outer query
|
||||
// are paired with collections of matching elements from inner. GroupJoin enables you
|
||||
// to base your results on a whole set of matches for each element of outer query.
|
||||
//
|
||||
// The resultSelector function is called only one time for each outer element
|
||||
// together with a collection of all the inner elements that match the outer
|
||||
// element. This differs from the Join method, in which the result selector
|
||||
// function is invoked on pairs that contain one element from outer and one
|
||||
// element from inner.
|
||||
// together with a collection of all the inner elements that match the outer element.
|
||||
// This differs from the Join method, in which the result selector function is invoked
|
||||
// on pairs that contain one element from outer and one element from inner.
|
||||
//
|
||||
// GroupJoin preserves the order of the elements of outer, and for each element
|
||||
// of outer, the order of the matching elements from inner.
|
||||
func (q Query) GroupJoin(inner Query,
|
||||
// GroupJoin preserves the order of the elements of outer, and for each element of outer,
|
||||
// the order of the matching elements from inner.
|
||||
func (q Query) GroupJoin(
|
||||
inner Query,
|
||||
outerKeySelector func(interface{}) interface{},
|
||||
innerKeySelector func(interface{}) interface{},
|
||||
resultSelector func(outer interface{}, inners []interface{}) interface{}) Query {
|
||||
resultSelector func(outer interface{}, inners []interface{}) interface{},
|
||||
) Query {
|
||||
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -50,58 +48,3 @@ func (q Query) GroupJoin(inner Query,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GroupJoinT is the typed version of GroupJoin.
|
||||
//
|
||||
// - inner: The query to join to the outer query.
|
||||
// - outerKeySelectorFn is of type "func(TOuter) TKey"
|
||||
// - innerKeySelectorFn is of type "func(TInner) TKey"
|
||||
// - resultSelectorFn: is of type "func(TOuter, inners []TInner) TResult"
|
||||
//
|
||||
// NOTE: GroupJoin has better performance than GroupJoinT.
|
||||
func (q Query) GroupJoinT(inner Query,
|
||||
outerKeySelectorFn interface{},
|
||||
innerKeySelectorFn interface{},
|
||||
resultSelectorFn interface{}) Query {
|
||||
outerKeySelectorGenericFunc, err := newGenericFunc(
|
||||
"GroupJoinT", "outerKeySelectorFn", outerKeySelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
outerKeySelectorFunc := func(item interface{}) interface{} {
|
||||
return outerKeySelectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
innerKeySelectorFuncGenericFunc, err := newGenericFunc(
|
||||
"GroupJoinT", "innerKeySelectorFn", innerKeySelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
innerKeySelectorFunc := func(item interface{}) interface{} {
|
||||
return innerKeySelectorFuncGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
resultSelectorGenericFunc, err := newGenericFunc(
|
||||
"GroupJoinT", "resultSelectorFn", resultSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultSelectorFunc := func(outer interface{}, inners []interface{}) interface{} {
|
||||
innerSliceType := reflect.MakeSlice(resultSelectorGenericFunc.Cache.TypesIn[1], 0, 0)
|
||||
innersSlicePointer := reflect.New(innerSliceType.Type())
|
||||
From(inners).ToSlice(innersSlicePointer.Interface())
|
||||
innersTyped := reflect.Indirect(innersSlicePointer).Interface()
|
||||
return resultSelectorGenericFunc.Call(outer, innersTyped)
|
||||
}
|
||||
|
||||
return q.GroupJoin(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc)
|
||||
}
|
||||
|
||||
+8
-28
@@ -2,8 +2,8 @@ package linq
|
||||
|
||||
// Intersect produces the set intersection of the source collection and the
|
||||
// provided input collection. The intersection of two sets A and B is defined as
|
||||
// the set that contains all the elements of A that also appear in B, but no
|
||||
// other elements.
|
||||
// the set that contains all the elements of A that also appear in B,
|
||||
// but no other elements.
|
||||
func (q Query) Intersect(q2 Query) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -31,12 +31,14 @@ func (q Query) Intersect(q2 Query) Query {
|
||||
|
||||
// IntersectBy produces the set intersection of the source collection and the
|
||||
// provided input collection. The intersection of two sets A and B is defined as
|
||||
// the set that contains all the elements of A that also appear in B, but no
|
||||
// other elements.
|
||||
// the set that contains all the elements of A that also appear in B,
|
||||
// but no other elements.
|
||||
//
|
||||
// IntersectBy invokes a transform function on each element of both collections.
|
||||
func (q Query) IntersectBy(q2 Query,
|
||||
selector func(interface{}) interface{}) Query {
|
||||
func (q Query) IntersectBy(
|
||||
q2 Query,
|
||||
selector func(interface{}) interface{},
|
||||
) Query {
|
||||
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -63,25 +65,3 @@ func (q Query) IntersectBy(q2 Query,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectByT is the typed version of IntersectBy.
|
||||
//
|
||||
// - selectorFn is of type "func(TSource) TSource"
|
||||
//
|
||||
// NOTE: IntersectBy has better performance than IntersectByT.
|
||||
func (q Query) IntersectByT(q2 Query,
|
||||
selectorFn interface{}) Query {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"IntersectByT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
return q.IntersectBy(q2, selectorFunc)
|
||||
}
|
||||
|
||||
+10
-59
@@ -2,18 +2,20 @@ package linq
|
||||
|
||||
// Join correlates the elements of two collection based on matching keys.
|
||||
//
|
||||
// A join refers to the operation of correlating the elements of two sources of
|
||||
// information based on a common key. Join brings the two information sources
|
||||
// and the keys by which they are matched together in one method call. This
|
||||
// differs from the use of SelectMany, which requires more than one method call
|
||||
// A join refers to the operation of correlating the elements of two sources
|
||||
// of information based on a common key. Join brings the two information sources
|
||||
// and the keys by which they are matched together in one method call.
|
||||
// This differs from the use of SelectMany, which requires more than one method call
|
||||
// to perform the same operation.
|
||||
//
|
||||
// Join preserves the order of the elements of outer collection, and for each of
|
||||
// these elements, the order of the matching elements of inner.
|
||||
func (q Query) Join(inner Query,
|
||||
// Join preserves the order of the elements of outer collection,
|
||||
// and for each of these elements, the order of the matching elements of inner.
|
||||
func (q Query) Join(
|
||||
inner Query,
|
||||
outerKeySelector func(interface{}) interface{},
|
||||
innerKeySelector func(interface{}) interface{},
|
||||
resultSelector func(outer interface{}, inner interface{}) interface{}) Query {
|
||||
resultSelector func(outer interface{}, inner interface{}) interface{},
|
||||
) Query {
|
||||
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -52,54 +54,3 @@ func (q Query) Join(inner Query,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// JoinT is the typed version of Join.
|
||||
//
|
||||
// - outerKeySelectorFn is of type "func(TOuter) TKey"
|
||||
// - innerKeySelectorFn is of type "func(TInner) TKey"
|
||||
// - resultSelectorFn is of type "func(TOuter,TInner) TResult"
|
||||
//
|
||||
// NOTE: Join has better performance than JoinT.
|
||||
func (q Query) JoinT(inner Query,
|
||||
outerKeySelectorFn interface{},
|
||||
innerKeySelectorFn interface{},
|
||||
resultSelectorFn interface{}) Query {
|
||||
outerKeySelectorGenericFunc, err := newGenericFunc(
|
||||
"JoinT", "outerKeySelectorFn", outerKeySelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
outerKeySelectorFunc := func(item interface{}) interface{} {
|
||||
return outerKeySelectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
innerKeySelectorFuncGenericFunc, err := newGenericFunc(
|
||||
"JoinT", "innerKeySelectorFn",
|
||||
innerKeySelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
innerKeySelectorFunc := func(item interface{}) interface{} {
|
||||
return innerKeySelectorFuncGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
resultSelectorGenericFunc, err := newGenericFunc(
|
||||
"JoinT", "resultSelectorFn", resultSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultSelectorFunc := func(outer interface{}, inner interface{}) interface{} {
|
||||
return resultSelectorGenericFunc.Call(outer, inner)
|
||||
}
|
||||
|
||||
return q.Join(inner, outerKeySelectorFunc, innerKeySelectorFunc, resultSelectorFunc)
|
||||
}
|
||||
|
||||
+20
-121
@@ -8,17 +8,18 @@ type order struct {
|
||||
desc bool
|
||||
}
|
||||
|
||||
// OrderedQuery is the type returned from OrderBy, OrderByDescending ThenBy and
|
||||
// ThenByDescending functions.
|
||||
// OrderedQuery is the type returned from OrderBy, OrderByDescending
|
||||
// ThenBy and ThenByDescending functions.
|
||||
type OrderedQuery struct {
|
||||
Query
|
||||
original Query
|
||||
orders []order
|
||||
}
|
||||
|
||||
// OrderBy sorts the elements of a collection in ascending order. Elements are
|
||||
// sorted according to a key.
|
||||
func (q Query) OrderBy(selector func(interface{}) interface{}) OrderedQuery {
|
||||
// OrderBy sorts the elements of a collection in ascending order.
|
||||
// Elements are sorted according to a key.
|
||||
func (q Query) OrderBy(
|
||||
selector func(interface{}) interface{}) OrderedQuery {
|
||||
return OrderedQuery{
|
||||
orders: []order{{selector: selector}},
|
||||
original: q,
|
||||
@@ -42,30 +43,10 @@ func (q Query) OrderBy(selector func(interface{}) interface{}) OrderedQuery {
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByT is the typed version of OrderBy.
|
||||
//
|
||||
// - selectorFn is of type "func(TSource) TKey"
|
||||
//
|
||||
// NOTE: OrderBy has better performance than OrderByT.
|
||||
func (q Query) OrderByT(selectorFn interface{}) OrderedQuery {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"OrderByT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
return q.OrderBy(selectorFunc)
|
||||
}
|
||||
|
||||
// OrderByDescending sorts the elements of a collection in descending order.
|
||||
// Elements are sorted according to a key.
|
||||
func (q Query) OrderByDescending(selector func(interface{}) interface{}) OrderedQuery {
|
||||
func (q Query) OrderByDescending(
|
||||
selector func(interface{}) interface{}) OrderedQuery {
|
||||
return OrderedQuery{
|
||||
orders: []order{{selector: selector, desc: true}},
|
||||
original: q,
|
||||
@@ -89,28 +70,9 @@ func (q Query) OrderByDescending(selector func(interface{}) interface{}) Ordered
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByDescendingT is the typed version of OrderByDescending.
|
||||
// - selectorFn is of type "func(TSource) TKey"
|
||||
// NOTE: OrderByDescending has better performance than OrderByDescendingT.
|
||||
func (q Query) OrderByDescendingT(selectorFn interface{}) OrderedQuery {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"OrderByDescendingT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
return q.OrderByDescending(selectorFunc)
|
||||
}
|
||||
|
||||
// ThenBy performs a subsequent ordering of the elements in a collection in
|
||||
// ascending order. This method enables you to specify multiple sort criteria by
|
||||
// applying any number of ThenBy or ThenByDescending methods.
|
||||
// ThenBy performs a subsequent ordering of the elements in a collection
|
||||
// in ascending order. This method enables you to specify multiple sort criteria
|
||||
// by applying any number of ThenBy or ThenByDescending methods.
|
||||
func (oq OrderedQuery) ThenBy(
|
||||
selector func(interface{}) interface{}) OrderedQuery {
|
||||
return OrderedQuery{
|
||||
@@ -136,29 +98,11 @@ func (oq OrderedQuery) ThenBy(
|
||||
}
|
||||
}
|
||||
|
||||
// ThenByT is the typed version of ThenBy.
|
||||
// - selectorFn is of type "func(TSource) TKey"
|
||||
// NOTE: ThenBy has better performance than ThenByT.
|
||||
func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"ThenByT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
return oq.ThenBy(selectorFunc)
|
||||
}
|
||||
|
||||
// ThenByDescending performs a subsequent ordering of the elements in a
|
||||
// collection in descending order. This method enables you to specify multiple
|
||||
// sort criteria by applying any number of ThenBy or ThenByDescending methods.
|
||||
func (oq OrderedQuery) ThenByDescending(selector func(interface{}) interface{}) OrderedQuery {
|
||||
// ThenByDescending performs a subsequent ordering of the elements in a collection
|
||||
// in descending order. This method enables you to specify multiple sort criteria
|
||||
// by applying any number of ThenBy or ThenByDescending methods.
|
||||
func (oq OrderedQuery) ThenByDescending(
|
||||
selector func(interface{}) interface{}) OrderedQuery {
|
||||
return OrderedQuery{
|
||||
orders: append(oq.orders, order{selector: selector, desc: true}),
|
||||
original: oq.original,
|
||||
@@ -182,32 +126,10 @@ func (oq OrderedQuery) ThenByDescending(selector func(interface{}) interface{})
|
||||
}
|
||||
}
|
||||
|
||||
// ThenByDescendingT is the typed version of ThenByDescending.
|
||||
// - selectorFn is of type "func(TSource) TKey"
|
||||
// NOTE: ThenByDescending has better performance than ThenByDescendingT.
|
||||
func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery {
|
||||
selectorFunc, ok := selectorFn.(func(interface{}) interface{})
|
||||
if !ok {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"ThenByDescending", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc = func(item interface{}) interface{} {
|
||||
return selectorGenericFunc.Call(item)
|
||||
}
|
||||
}
|
||||
return oq.ThenByDescending(selectorFunc)
|
||||
}
|
||||
|
||||
// Sort returns a new query by sorting elements with provided less function in
|
||||
// ascending order. The comparer function should return true if the parameter i
|
||||
// is less than j. While this method is uglier than chaining OrderBy,
|
||||
// OrderByDescending, ThenBy and ThenByDescending methods, it's performance is
|
||||
// much better.
|
||||
// Sort returns a new query by sorting elements with provided less function
|
||||
// in ascending order. The comparer function should return true if the parameter i
|
||||
// is less than j. While this method is uglier than chaining OrderBy, OrderByDescending,
|
||||
// ThenBy and ThenByDescending methods, it's performance is much better.
|
||||
func (q Query) Sort(less func(i, j interface{}) bool) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -228,25 +150,6 @@ func (q Query) Sort(less func(i, j interface{}) bool) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// SortT is the typed version of Sort.
|
||||
// - lessFn is of type "func(TSource,TSource) bool"
|
||||
// NOTE: Sort has better performance than SortT.
|
||||
func (q Query) SortT(lessFn interface{}) Query {
|
||||
lessGenericFunc, err := newGenericFunc(
|
||||
"SortT", "lessFn", lessFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lessFunc := func(i, j interface{}) bool {
|
||||
return lessGenericFunc.Call(i, j).(bool)
|
||||
}
|
||||
|
||||
return q.Sort(lessFunc)
|
||||
}
|
||||
|
||||
type sorter struct {
|
||||
items []interface{}
|
||||
less func(i, j interface{}) bool
|
||||
@@ -270,10 +173,6 @@ func (q Query) sort(orders []order) (r []interface{}) {
|
||||
r = append(r, item)
|
||||
}
|
||||
|
||||
if len(r) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i, j := range orders {
|
||||
orders[i].compare = getComparer(j.selector(r[0]))
|
||||
}
|
||||
|
||||
+33
-297
@@ -18,27 +18,6 @@ func (q Query) All(predicate func(interface{}) bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// AllT is the typed version of All.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource) bool"
|
||||
//
|
||||
// NOTE: All has better performance than AllT.
|
||||
func (q Query) AllT(predicateFn interface{}) bool {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"AllT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.All(predicateFunc)
|
||||
}
|
||||
|
||||
// Any determines whether any element of a collection exists.
|
||||
func (q Query) Any() bool {
|
||||
_, ok := q.Iterate()()
|
||||
@@ -58,28 +37,6 @@ func (q Query) AnyWith(predicate func(interface{}) bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyWithT is the typed version of AnyWith.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource) bool"
|
||||
//
|
||||
// NOTE: AnyWith has better performance than AnyWithT.
|
||||
func (q Query) AnyWithT(predicateFn interface{}) bool {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"AnyWithT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.AnyWith(predicateFunc)
|
||||
}
|
||||
|
||||
// Average computes the average of a collection of numeric values.
|
||||
func (q Query) Average() (r float64) {
|
||||
next := q.Iterate()
|
||||
@@ -147,8 +104,8 @@ func (q Query) Count() (r int) {
|
||||
return
|
||||
}
|
||||
|
||||
// CountWith returns a number that represents how many elements in the specified
|
||||
// collection satisfy a condition.
|
||||
// CountWith returns a number that represents how many elements
|
||||
// in the specified collection satisfy a condition.
|
||||
func (q Query) CountWith(predicate func(interface{}) bool) (r int) {
|
||||
next := q.Iterate()
|
||||
|
||||
@@ -161,36 +118,14 @@ func (q Query) CountWith(predicate func(interface{}) bool) (r int) {
|
||||
return
|
||||
}
|
||||
|
||||
// CountWithT is the typed version of CountWith.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource) bool"
|
||||
//
|
||||
// NOTE: CountWith has better performance than CountWithT.
|
||||
func (q Query) CountWithT(predicateFn interface{}) int {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"CountWithT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.CountWith(predicateFunc)
|
||||
}
|
||||
|
||||
// First returns the first element of a collection.
|
||||
func (q Query) First() interface{} {
|
||||
item, _ := q.Iterate()()
|
||||
return item
|
||||
}
|
||||
|
||||
// FirstWith returns the first element of a collection that satisfies a
|
||||
// specified condition.
|
||||
// FirstWith returns the first element of a collection that satisfies
|
||||
// a specified condition.
|
||||
func (q Query) FirstWith(predicate func(interface{}) bool) interface{} {
|
||||
next := q.Iterate()
|
||||
|
||||
@@ -203,99 +138,6 @@ func (q Query) FirstWith(predicate func(interface{}) bool) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FirstWithT is the typed version of FirstWith.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource) bool"
|
||||
//
|
||||
// NOTE: FirstWith has better performance than FirstWithT.
|
||||
func (q Query) FirstWithT(predicateFn interface{}) interface{} {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"FirstWithT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.FirstWith(predicateFunc)
|
||||
}
|
||||
|
||||
// ForEach performs the specified action on each element of a collection.
|
||||
func (q Query) ForEach(action func(interface{})) {
|
||||
next := q.Iterate()
|
||||
|
||||
for item, ok := next(); ok; item, ok = next() {
|
||||
action(item)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachT is the typed version of ForEach.
|
||||
//
|
||||
// - actionFn is of type "func(TSource)"
|
||||
//
|
||||
// NOTE: ForEach has better performance than ForEachT.
|
||||
func (q Query) ForEachT(actionFn interface{}) {
|
||||
actionGenericFunc, err := newGenericFunc(
|
||||
"ForEachT", "actionFn", actionFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), nil),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
actionFunc := func(item interface{}) {
|
||||
actionGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
q.ForEach(actionFunc)
|
||||
}
|
||||
|
||||
// ForEachIndexed performs the specified action on each element of a collection.
|
||||
//
|
||||
// The first argument to action represents the zero-based index of that
|
||||
// element in the source collection. This can be useful if the elements are in a
|
||||
// known order and you want to do something with an element at a particular
|
||||
// index, for example. It can also be useful if you want to retrieve the index
|
||||
// of one or more elements. The second argument to action represents the
|
||||
// element to process.
|
||||
func (q Query) ForEachIndexed(action func(int, interface{})) {
|
||||
next := q.Iterate()
|
||||
index := 0
|
||||
|
||||
for item, ok := next(); ok; item, ok = next() {
|
||||
action(index, item)
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachIndexedT is the typed version of ForEachIndexed.
|
||||
//
|
||||
// - actionFn is of type "func(int, TSource)"
|
||||
//
|
||||
// NOTE: ForEachIndexed has better performance than ForEachIndexedT.
|
||||
func (q Query) ForEachIndexedT(actionFn interface{}) {
|
||||
actionGenericFunc, err := newGenericFunc(
|
||||
"ForEachIndexedT", "actionFn", actionFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), nil),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
actionFunc := func(index int, item interface{}) {
|
||||
actionGenericFunc.Call(index, item)
|
||||
}
|
||||
|
||||
q.ForEachIndexed(actionFunc)
|
||||
}
|
||||
|
||||
// Last returns the last element of a collection.
|
||||
func (q Query) Last() (r interface{}) {
|
||||
next := q.Iterate()
|
||||
@@ -307,8 +149,8 @@ func (q Query) Last() (r interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// LastWith returns the last element of a collection that satisfies a specified
|
||||
// condition.
|
||||
// LastWith returns the last element of a collection that satisfies
|
||||
// a specified condition.
|
||||
func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) {
|
||||
next := q.Iterate()
|
||||
|
||||
@@ -321,28 +163,6 @@ func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// LastWithT is the typed version of LastWith.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource) bool"
|
||||
//
|
||||
// NOTE: LastWith has better performance than LastWithT.
|
||||
func (q Query) LastWithT(predicateFn interface{}) interface{} {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"LastWithT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.LastWith(predicateFunc)
|
||||
}
|
||||
|
||||
// Max returns the maximum value in a collection of values.
|
||||
func (q Query) Max() (r interface{}) {
|
||||
next := q.Iterate()
|
||||
@@ -410,8 +230,8 @@ func (q Query) SequenceEqual(q2 Query) bool {
|
||||
return !ok2
|
||||
}
|
||||
|
||||
// Single returns the only element of a collection, and nil if there is not
|
||||
// exactly one element in the collection.
|
||||
// Single returns the only element of a collection, and nil
|
||||
// if there is not exactly one element in the collection.
|
||||
func (q Query) Single() interface{} {
|
||||
next := q.Iterate()
|
||||
item, ok := next()
|
||||
@@ -427,8 +247,8 @@ func (q Query) Single() interface{} {
|
||||
return item
|
||||
}
|
||||
|
||||
// SingleWith returns the only element of a collection that satisfies a
|
||||
// specified condition, and nil if more than one such element exists.
|
||||
// SingleWith returns the only element of a collection that satisfies
|
||||
// a specified condition, and nil if more than one such element exists.
|
||||
func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) {
|
||||
next := q.Iterate()
|
||||
found := false
|
||||
@@ -447,31 +267,10 @@ func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// SingleWithT is the typed version of SingleWith.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource) bool"
|
||||
//
|
||||
// NOTE: SingleWith has better performance than SingleWithT.
|
||||
func (q Query) SingleWithT(predicateFn interface{}) interface{} {
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"SingleWithT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.SingleWith(predicateFunc)
|
||||
}
|
||||
|
||||
// SumInts computes the sum of a collection of numeric values.
|
||||
//
|
||||
// Values can be of any integer type: int, int8, int16, int32, int64. The result
|
||||
// is int64. Method returns zero if collection contains no elements.
|
||||
// Values can be of any integer type: int, int8, int16, int32, int64.
|
||||
// The result is int64. Method returns zero if collection contains no elements.
|
||||
func (q Query) SumInts() (r int64) {
|
||||
next := q.Iterate()
|
||||
item, ok := next()
|
||||
@@ -491,9 +290,8 @@ func (q Query) SumInts() (r int64) {
|
||||
|
||||
// SumUInts computes the sum of a collection of numeric values.
|
||||
//
|
||||
// Values can be of any unsigned integer type: uint, uint8, uint16, uint32,
|
||||
// uint64. The result is uint64. Method returns zero if collection contains no
|
||||
// elements.
|
||||
// Values can be of any unsigned integer type: uint, uint8, uint16, uint32, uint64.
|
||||
// The result is uint64. Method returns zero if collection contains no elements.
|
||||
func (q Query) SumUInts() (r uint64) {
|
||||
next := q.Iterate()
|
||||
item, ok := next()
|
||||
@@ -532,8 +330,8 @@ func (q Query) SumFloats() (r float64) {
|
||||
return
|
||||
}
|
||||
|
||||
// ToChannel iterates over a collection and outputs each element to a channel,
|
||||
// then closes it.
|
||||
// ToChannel iterates over a collection and outputs each element
|
||||
// to a channel, then closes it.
|
||||
func (q Query) ToChannel(result chan<- interface{}) {
|
||||
next := q.Iterate()
|
||||
|
||||
@@ -545,9 +343,8 @@ func (q Query) ToChannel(result chan<- interface{}) {
|
||||
}
|
||||
|
||||
// ToMap iterates over a collection and populates result map with elements.
|
||||
// Collection elements have to be of KeyValue type to use this method. To
|
||||
// populate a map with elements of different type use ToMapBy method. ToMap
|
||||
// doesn't empty the result map before populating it.
|
||||
// Collection elements have to be of KeyValue type to use this method.
|
||||
// To populate a map with elements of different type use ToMapBy method.
|
||||
func (q Query) ToMap(result interface{}) {
|
||||
q.ToMapBy(
|
||||
result,
|
||||
@@ -559,14 +356,15 @@ func (q Query) ToMap(result interface{}) {
|
||||
})
|
||||
}
|
||||
|
||||
// ToMapBy iterates over a collection and populates the result map with
|
||||
// elements. Functions keySelector and valueSelector are executed for each
|
||||
// element of the collection to generate key and value for the map. Generated
|
||||
// key and value types must be assignable to the map's key and value types.
|
||||
// ToMapBy doesn't empty the result map before populating it.
|
||||
func (q Query) ToMapBy(result interface{},
|
||||
// ToMapBy iterates over a collection and populates result map with elements.
|
||||
// Functions keySelector and valueSelector are executed for each element of the collection
|
||||
// to generate key and value for the map. Generated key and value types must be assignable
|
||||
// to the map's key and value types.
|
||||
func (q Query) ToMapBy(
|
||||
result interface{},
|
||||
keySelector func(interface{}) interface{},
|
||||
valueSelector func(interface{}) interface{}) {
|
||||
valueSelector func(interface{}) interface{},
|
||||
) {
|
||||
res := reflect.ValueOf(result)
|
||||
m := reflect.Indirect(res)
|
||||
next := q.Iterate()
|
||||
@@ -581,78 +379,16 @@ func (q Query) ToMapBy(result interface{},
|
||||
res.Elem().Set(m)
|
||||
}
|
||||
|
||||
// ToMapByT is the typed version of ToMapBy.
|
||||
//
|
||||
// - keySelectorFn is of type "func(TSource)TKey"
|
||||
// - valueSelectorFn is of type "func(TSource)TValue"
|
||||
//
|
||||
// NOTE: ToMapBy has better performance than ToMapByT.
|
||||
func (q Query) ToMapByT(result interface{},
|
||||
keySelectorFn interface{}, valueSelectorFn interface{}) {
|
||||
keySelectorGenericFunc, err := newGenericFunc(
|
||||
"ToMapByT", "keySelectorFn", keySelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keySelectorFunc := func(item interface{}) interface{} {
|
||||
return keySelectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
valueSelectorGenericFunc, err := newGenericFunc(
|
||||
"ToMapByT", "valueSelectorFn", valueSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
valueSelectorFunc := func(item interface{}) interface{} {
|
||||
return valueSelectorGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
q.ToMapBy(result, keySelectorFunc, valueSelectorFunc)
|
||||
}
|
||||
|
||||
// ToSlice iterates over a collection and saves the results in the slice pointed
|
||||
// by v. It overwrites the existing slice, starting from index 0.
|
||||
//
|
||||
// If the slice pointed by v has sufficient capacity, v will be pointed to a
|
||||
// resliced slice. If it does not, a new underlying array will be allocated and
|
||||
// v will point to it.
|
||||
func (q Query) ToSlice(v interface{}) {
|
||||
res := reflect.ValueOf(v)
|
||||
// ToSlice iterates over a collection and populates result slice with elements.
|
||||
// Collection elements must be assignable to the slice's element type.
|
||||
func (q Query) ToSlice(result interface{}) {
|
||||
res := reflect.ValueOf(result)
|
||||
slice := reflect.Indirect(res)
|
||||
|
||||
cap := slice.Cap()
|
||||
res.Elem().Set(slice.Slice(0, cap)) // make len(slice)==cap(slice) from now on
|
||||
|
||||
next := q.Iterate()
|
||||
index := 0
|
||||
|
||||
for item, ok := next(); ok; item, ok = next() {
|
||||
if index >= cap {
|
||||
slice, cap = grow(slice)
|
||||
}
|
||||
slice.Index(index).Set(reflect.ValueOf(item))
|
||||
index++
|
||||
slice = reflect.Append(slice, reflect.ValueOf(item))
|
||||
}
|
||||
|
||||
// reslice the len(res)==cap(res) actual res size
|
||||
res.Elem().Set(slice.Slice(0, index))
|
||||
}
|
||||
|
||||
// grow grows the slice s by doubling its capacity, then it returns the new
|
||||
// slice (resliced to its full capacity) and the new capacity.
|
||||
func grow(s reflect.Value) (v reflect.Value, newCap int) {
|
||||
cap := s.Cap()
|
||||
if cap == 0 {
|
||||
cap = 1
|
||||
} else {
|
||||
cap *= 2
|
||||
}
|
||||
newSlice := reflect.MakeSlice(s.Type(), cap, cap)
|
||||
reflect.Copy(newSlice, s)
|
||||
return newSlice, cap
|
||||
res.Elem().Set(slice)
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@ package linq
|
||||
|
||||
// Reverse inverts the order of the elements in a collection.
|
||||
//
|
||||
// Unlike OrderBy, this sorting method does not consider the actual values
|
||||
// themselves in determining the order. Rather, it just returns the elements in
|
||||
// the reverse order from which they are produced by the underlying source.
|
||||
// Unlike OrderBy, this sorting method does not consider the actual values themselves
|
||||
// in determining the order. Rather, it just returns the elements in the reverse order
|
||||
// from which they are produced by the underlying source.
|
||||
func (q Query) Reverse() Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
|
||||
+27
-65
@@ -1,16 +1,17 @@
|
||||
package linq
|
||||
|
||||
// Select projects each element of a collection into a new form. Returns a query
|
||||
// with the result of invoking the transform function on each element of
|
||||
// original source.
|
||||
// Select projects each element of a collection into a new form.
|
||||
// Returns a query with the result of invoking the transform function
|
||||
// on each element of original source.
|
||||
//
|
||||
// This projection method requires the transform function, selector, to produce
|
||||
// one value for each value in the source collection. If selector returns a
|
||||
// value that is itself a collection, it is up to the consumer to traverse the
|
||||
// subcollections manually. In such a situation, it might be better for your
|
||||
// query to return a single coalesced collection of values. To achieve this, use
|
||||
// the SelectMany method instead of Select. Although SelectMany works similarly
|
||||
// to Select, it differs in that the transform function returns a collection
|
||||
// This projection method requires the transform function, selector,
|
||||
// to produce one value for each value in the source collection.
|
||||
// If selector returns a value that is itself a collection,
|
||||
// it is up to the consumer to traverse the subcollections manually.
|
||||
// In such a situation, it might be better for your query to return a single
|
||||
// coalesced collection of values. To achieve this, use the SelectMany method
|
||||
// instead of Select. Although SelectMany works similarly to Select,
|
||||
// it differs in that the transform function returns a collection
|
||||
// that is then expanded by SelectMany before it is returned.
|
||||
func (q Query) Select(selector func(interface{}) interface{}) Query {
|
||||
return Query{
|
||||
@@ -30,44 +31,24 @@ func (q Query) Select(selector func(interface{}) interface{}) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// SelectT is the typed version of Select.
|
||||
// - selectorFn is of type "func(TSource)TResult"
|
||||
// NOTE: Select has better performance than SelectT.
|
||||
func (q Query) SelectT(selectorFn interface{}) Query {
|
||||
|
||||
selectGenericFunc, err := newGenericFunc(
|
||||
"SelectT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(item interface{}) interface{} {
|
||||
return selectGenericFunc.Call(item)
|
||||
}
|
||||
|
||||
return q.Select(selectorFunc)
|
||||
}
|
||||
|
||||
// SelectIndexed projects each element of a collection into a new form by
|
||||
// incorporating the element's index. Returns a query with the result of
|
||||
// invoking the transform function on each element of original source.
|
||||
// SelectIndexed projects each element of a collection into a new form
|
||||
// by incorporating the element's index. Returns a query with the result
|
||||
// of invoking the transform function on each element of original source.
|
||||
//
|
||||
// The first argument to selector represents the zero-based index of that
|
||||
// element in the source collection. This can be useful if the elements are in a
|
||||
// known order and you want to do something with an element at a particular
|
||||
// index, for example. It can also be useful if you want to retrieve the index
|
||||
// of one or more elements. The second argument to selector represents the
|
||||
// element to process.
|
||||
// The first argument to selector represents the zero-based index of that element
|
||||
// in the source collection. This can be useful if the elements are in a known order
|
||||
// and you want to do something with an element at a particular index,
|
||||
// for example. It can also be useful if you want to retrieve the index of one
|
||||
// or more elements. The second argument to selector represents the element to process.
|
||||
//
|
||||
// This projection method requires the transform function, selector, to produce
|
||||
// one value for each value in the source collection. If selector returns a
|
||||
// value that is itself a collection, it is up to the consumer to traverse the
|
||||
// subcollections manually. In such a situation, it might be better for your
|
||||
// query to return a single coalesced collection of values. To achieve this, use
|
||||
// the SelectMany method instead of Select. Although SelectMany works similarly
|
||||
// to Select, it differs in that the transform function returns a collection
|
||||
// This projection method requires the transform function, selector,
|
||||
// to produce one value for each value in the source collection.
|
||||
// If selector returns a value that is itself a collection,
|
||||
// it is up to the consumer to traverse the subcollections manually.
|
||||
// In such a situation, it might be better for your query to return a single
|
||||
// coalesced collection of values. To achieve this, use the SelectMany method
|
||||
// instead of Select. Although SelectMany works similarly to Select,
|
||||
// it differs in that the transform function returns a collection
|
||||
// that is then expanded by SelectMany before it is returned.
|
||||
func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query {
|
||||
return Query{
|
||||
@@ -88,22 +69,3 @@ func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SelectIndexedT is the typed version of SelectIndexed.
|
||||
// - selectorFn is of type "func(int,TSource)TResult"
|
||||
// NOTE: SelectIndexed has better performance than SelectIndexedT.
|
||||
func (q Query) SelectIndexedT(selectorFn interface{}) Query {
|
||||
selectGenericFunc, err := newGenericFunc(
|
||||
"SelectIndexedT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(index int, item interface{}) interface{} {
|
||||
return selectGenericFunc.Call(index, item)
|
||||
}
|
||||
|
||||
return q.SelectIndexed(selectorFunc)
|
||||
}
|
||||
|
||||
+20
-134
@@ -32,37 +32,14 @@ func (q Query) SelectMany(selector func(interface{}) Query) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// SelectManyT is the typed version of SelectMany.
|
||||
// SelectManyIndexed projects each element of a collection to a Query, iterates and
|
||||
// flattens the resulting collection into one collection.
|
||||
//
|
||||
// - selectorFn is of type "func(TSource)Query"
|
||||
//
|
||||
// NOTE: SelectMany has better performance than SelectManyT.
|
||||
func (q Query) SelectManyT(selectorFn interface{}) Query {
|
||||
|
||||
selectManyGenericFunc, err := newGenericFunc(
|
||||
"SelectManyT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(inner interface{}) Query {
|
||||
return selectManyGenericFunc.Call(inner).(Query)
|
||||
}
|
||||
return q.SelectMany(selectorFunc)
|
||||
|
||||
}
|
||||
|
||||
// SelectManyIndexed projects each element of a collection to a Query, iterates
|
||||
// and flattens the resulting collection into one collection.
|
||||
//
|
||||
// The first argument to selector represents the zero-based index of that
|
||||
// element in the source collection. This can be useful if the elements are in a
|
||||
// known order and you want to do something with an element at a particular
|
||||
// index, for example. It can also be useful if you want to retrieve the index
|
||||
// of one or more elements. The second argument to selector represents the
|
||||
// element to process.
|
||||
// The first argument to selector represents the zero-based index of that element
|
||||
// in the source collection. This can be useful if the elements are in a known order
|
||||
// and you want to do something with an element at a particular index, for example.
|
||||
// It can also be useful if you want to retrieve the index of one or more elements.
|
||||
// The second argument to selector represents the element to process.
|
||||
func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -95,33 +72,13 @@ func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// SelectManyIndexedT is the typed version of SelectManyIndexed.
|
||||
//
|
||||
// - selectorFn is of type "func(int,TSource)Query"
|
||||
//
|
||||
// NOTE: SelectManyIndexed has better performance than SelectManyIndexedT.
|
||||
func (q Query) SelectManyIndexedT(selectorFn interface{}) Query {
|
||||
|
||||
selectManyIndexedGenericFunc, err := newGenericFunc(
|
||||
"SelectManyIndexedT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(index int, inner interface{}) Query {
|
||||
return selectManyIndexedGenericFunc.Call(index, inner).(Query)
|
||||
}
|
||||
|
||||
return q.SelectManyIndexed(selectorFunc)
|
||||
}
|
||||
|
||||
// SelectManyBy projects each element of a collection to a Query, iterates and
|
||||
// flattens the resulting collection into one collection, and invokes a result
|
||||
// selector function on each element therein.
|
||||
func (q Query) SelectManyBy(selector func(interface{}) Query,
|
||||
resultSelector func(interface{}, interface{}) interface{}) Query {
|
||||
// flattens the resulting collection into one collection, and invokes
|
||||
// a result selector function on each element therein.
|
||||
func (q Query) SelectManyBy(
|
||||
selector func(interface{}) Query,
|
||||
resultSelector func(interface{}, interface{}) interface{},
|
||||
) Query {
|
||||
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -146,53 +103,18 @@ func (q Query) SelectManyBy(selector func(interface{}) Query,
|
||||
}
|
||||
}
|
||||
|
||||
item = resultSelector(item, outer)
|
||||
item = resultSelector(outer, item)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SelectManyByT is the typed version of SelectManyBy.
|
||||
//
|
||||
// - selectorFn is of type "func(TSource)Query"
|
||||
// - resultSelectorFn is of type "func(TSource,TCollection)TResult"
|
||||
//
|
||||
// NOTE: SelectManyBy has better performance than SelectManyByT.
|
||||
func (q Query) SelectManyByT(selectorFn interface{},
|
||||
resultSelectorFn interface{}) Query {
|
||||
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"SelectManyByT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(Query))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(outer interface{}) Query {
|
||||
return selectorGenericFunc.Call(outer).(Query)
|
||||
}
|
||||
|
||||
resultSelectorGenericFunc, err := newGenericFunc(
|
||||
"SelectManyByT", "resultSelectorFn", resultSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultSelectorFunc := func(outer interface{}, item interface{}) interface{} {
|
||||
return resultSelectorGenericFunc.Call(outer, item)
|
||||
}
|
||||
|
||||
return q.SelectManyBy(selectorFunc, resultSelectorFunc)
|
||||
}
|
||||
|
||||
// SelectManyByIndexed projects each element of a collection to a Query,
|
||||
// iterates and flattens the resulting collection into one collection, and
|
||||
// invokes a result selector function on each element therein. The index of each
|
||||
// source element is used in the intermediate projected form of that element.
|
||||
// SelectManyByIndexed projects each element of a collection to a Query, iterates and
|
||||
// flattens the resulting collection into one collection, and invokes
|
||||
// a result selector function on each element therein.
|
||||
// The index of each source element is used in the intermediate projected form
|
||||
// of that element.
|
||||
func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query,
|
||||
resultSelector func(interface{}, interface{}) interface{}) Query {
|
||||
|
||||
@@ -221,45 +143,9 @@ func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query,
|
||||
}
|
||||
}
|
||||
|
||||
item = resultSelector(item, outer)
|
||||
item = resultSelector(outer, item)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SelectManyByIndexedT is the typed version of SelectManyByIndexed.
|
||||
//
|
||||
// - selectorFn is of type "func(int,TSource)Query"
|
||||
// - resultSelectorFn is of type "func(TSource,TCollection)TResult"
|
||||
//
|
||||
// NOTE: SelectManyByIndexed has better performance than
|
||||
// SelectManyByIndexedT.
|
||||
func (q Query) SelectManyByIndexedT(selectorFn interface{},
|
||||
resultSelectorFn interface{}) Query {
|
||||
selectorGenericFunc, err := newGenericFunc(
|
||||
"SelectManyByIndexedT", "selectorFn", selectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(Query))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
selectorFunc := func(index int, outer interface{}) Query {
|
||||
return selectorGenericFunc.Call(index, outer).(Query)
|
||||
}
|
||||
|
||||
resultSelectorGenericFunc, err := newGenericFunc(
|
||||
"SelectManyByIndexedT", "resultSelectorFn", resultSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultSelectorFunc := func(outer interface{}, item interface{}) interface{} {
|
||||
return resultSelectorGenericFunc.Call(outer, item)
|
||||
}
|
||||
|
||||
return q.SelectManyByIndexed(selectorFunc, resultSelectorFunc)
|
||||
}
|
||||
|
||||
+15
-58
@@ -1,7 +1,7 @@
|
||||
package linq
|
||||
|
||||
// Skip bypasses a specified number of elements in a collection and then returns
|
||||
// the remaining elements.
|
||||
// Skip bypasses a specified number of elements in a collection
|
||||
// and then returns the remaining elements.
|
||||
func (q Query) Skip(count int) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -22,13 +22,13 @@ func (q Query) Skip(count int) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// SkipWhile bypasses elements in a collection as long as a specified condition
|
||||
// is true and then returns the remaining elements.
|
||||
// SkipWhile bypasses elements in a collection as long as a specified condition is true
|
||||
// and then returns the remaining elements.
|
||||
//
|
||||
// This method tests each element by using predicate and skips the element if
|
||||
// the result is true. After the predicate function returns false for an
|
||||
// element, that element and the remaining elements in source are returned and
|
||||
// there are no more invocations of predicate.
|
||||
// This method tests each element by using predicate and skips the element
|
||||
// if the result is true. After the predicate function returns false for an element,
|
||||
// that element and the remaining elements in source are returned
|
||||
// and there are no more invocations of predicate.
|
||||
func (q Query) SkipWhile(predicate func(interface{}) bool) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -54,36 +54,14 @@ func (q Query) SkipWhile(predicate func(interface{}) bool) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// SkipWhileT is the typed version of SkipWhile.
|
||||
// SkipWhileIndexed bypasses elements in a collection as long as a specified condition
|
||||
// is true and then returns the remaining elements. The element's index is used
|
||||
// in the logic of the predicate function.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource)bool"
|
||||
//
|
||||
// NOTE: SkipWhile has better performance than SkipWhileT.
|
||||
func (q Query) SkipWhileT(predicateFn interface{}) Query {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"SkipWhileT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.SkipWhile(predicateFunc)
|
||||
}
|
||||
|
||||
// SkipWhileIndexed bypasses elements in a collection as long as a specified
|
||||
// condition is true and then returns the remaining elements. The element's
|
||||
// index is used in the logic of the predicate function.
|
||||
//
|
||||
// This method tests each element by using predicate and skips the element if
|
||||
// the result is true. After the predicate function returns false for an
|
||||
// element, that element and the remaining elements in source are returned and
|
||||
// there are no more invocations of predicate.
|
||||
// This method tests each element by using predicate and skips the element
|
||||
// if the result is true. After the predicate function returns false for an element,
|
||||
// that element and the remaining elements in source are returned
|
||||
// and there are no more invocations of predicate.
|
||||
func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -111,24 +89,3 @@ func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SkipWhileIndexedT is the typed version of SkipWhileIndexed.
|
||||
//
|
||||
// - predicateFn is of type "func(int,TSource)bool"
|
||||
//
|
||||
// NOTE: SkipWhileIndexed has better performance than SkipWhileIndexedT.
|
||||
func (q Query) SkipWhileIndexedT(predicateFn interface{}) Query {
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"SkipWhileIndexedT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(index int, item interface{}) bool {
|
||||
return predicateGenericFunc.Call(index, item).(bool)
|
||||
}
|
||||
|
||||
return q.SkipWhileIndexed(predicateFunc)
|
||||
}
|
||||
|
||||
+7
-52
@@ -1,7 +1,6 @@
|
||||
package linq
|
||||
|
||||
// Take returns a specified number of contiguous elements from the start of a
|
||||
// collection.
|
||||
// Take returns a specified number of contiguous elements from the start of a collection.
|
||||
func (q Query) Take(count int) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -20,8 +19,8 @@ func (q Query) Take(count int) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// TakeWhile returns elements from a collection as long as a specified condition
|
||||
// is true, and then skips the remaining elements.
|
||||
// TakeWhile returns elements from a collection as long as a specified condition is true,
|
||||
// and then skips the remaining elements.
|
||||
func (q Query) TakeWhile(predicate func(interface{}) bool) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -50,33 +49,10 @@ func (q Query) TakeWhile(predicate func(interface{}) bool) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// TakeWhileT is the typed version of TakeWhile.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource)bool"
|
||||
//
|
||||
// NOTE: TakeWhile has better performance than TakeWhileT.
|
||||
func (q Query) TakeWhileT(predicateFn interface{}) Query {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"TakeWhileT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.TakeWhile(predicateFunc)
|
||||
}
|
||||
|
||||
// TakeWhileIndexed returns elements from a collection as long as a specified
|
||||
// condition is true. The element's index is used in the logic of the predicate
|
||||
// function. The first argument of predicate represents the zero-based index of
|
||||
// the element within collection. The second argument represents the element to
|
||||
// test.
|
||||
// TakeWhileIndexed returns elements from a collection as long as a specified condition
|
||||
// is true. The element's index is used in the logic of the predicate function.
|
||||
// The first argument of predicate represents the zero-based index of the element
|
||||
// within collection. The second argument represents the element to test.
|
||||
func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -106,24 +82,3 @@ func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TakeWhileIndexedT is the typed version of TakeWhileIndexed.
|
||||
//
|
||||
// - predicateFn is of type "func(int,TSource)bool"
|
||||
//
|
||||
// NOTE: TakeWhileIndexed has better performance than TakeWhileIndexedT.
|
||||
func (q Query) TakeWhileIndexedT(predicateFn interface{}) Query {
|
||||
whereFunc, err := newGenericFunc(
|
||||
"TakeWhileIndexedT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(index int, item interface{}) bool {
|
||||
return whereFunc.Call(index, item).(bool)
|
||||
}
|
||||
|
||||
return q.TakeWhileIndexed(predicateFunc)
|
||||
}
|
||||
|
||||
+4
-3
@@ -2,9 +2,10 @@ package linq
|
||||
|
||||
// Union produces the set union of two collections.
|
||||
//
|
||||
// This method excludes duplicates from the return set. This is different
|
||||
// behavior to the Concat method, which returns all the elements in the input
|
||||
// collection including duplicates.
|
||||
// This method excludes duplicates from the return set.
|
||||
// This is different behavior to the Concat method,
|
||||
// which returns all the elements in the input collection
|
||||
// including duplicates.
|
||||
func (q Query) Union(q2 Query) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
|
||||
+4
-47
@@ -19,33 +19,11 @@ func (q Query) Where(predicate func(interface{}) bool) Query {
|
||||
}
|
||||
}
|
||||
|
||||
// WhereT is the typed version of Where.
|
||||
// WhereIndexed filters a collection of values based on a predicate.
|
||||
// Each element's index is used in the logic of the predicate function.
|
||||
//
|
||||
// - predicateFn is of type "func(TSource)bool"
|
||||
//
|
||||
// NOTE: Where has better performance than WhereT.
|
||||
func (q Query) WhereT(predicateFn interface{}) Query {
|
||||
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"WhereT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(item interface{}) bool {
|
||||
return predicateGenericFunc.Call(item).(bool)
|
||||
}
|
||||
|
||||
return q.Where(predicateFunc)
|
||||
}
|
||||
|
||||
// WhereIndexed filters a collection of values based on a predicate. Each
|
||||
// element's index is used in the logic of the predicate function.
|
||||
//
|
||||
// The first argument represents the zero-based index of the element within
|
||||
// collection. The second argument of predicate represents the element to test.
|
||||
// The first argument represents the zero-based index of the element within collection.
|
||||
// The second argument of predicate represents the element to test.
|
||||
func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query {
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -66,24 +44,3 @@ func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WhereIndexedT is the typed version of WhereIndexed.
|
||||
//
|
||||
// - predicateFn is of type "func(int,TSource)bool"
|
||||
//
|
||||
// NOTE: WhereIndexed has better performance than WhereIndexedT.
|
||||
func (q Query) WhereIndexedT(predicateFn interface{}) Query {
|
||||
predicateGenericFunc, err := newGenericFunc(
|
||||
"WhereIndexedT", "predicateFn", predicateFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(int), new(genericType)), newElemTypeSlice(new(bool))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
predicateFunc := func(index int, item interface{}) bool {
|
||||
return predicateGenericFunc.Call(index, item).(bool)
|
||||
}
|
||||
|
||||
return q.WhereIndexed(predicateFunc)
|
||||
}
|
||||
|
||||
+12
-32
@@ -1,17 +1,19 @@
|
||||
package linq
|
||||
|
||||
// Zip applies a specified function to the corresponding elements of two
|
||||
// collections, producing a collection of the results.
|
||||
// Zip applies a specified function to the corresponding elements
|
||||
// of two collections, producing a collection of the results.
|
||||
//
|
||||
// The method steps through the two input collections, applying function
|
||||
// resultSelector to corresponding elements of the two collections. The method
|
||||
// returns a collection of the values that are returned by resultSelector. If
|
||||
// the input collections do not have the same number of elements, the method
|
||||
// combines elements until it reaches the end of one of the collections. For
|
||||
// example, if one collection has three elements and the other one has four, the
|
||||
// result collection has only three elements.
|
||||
func (q Query) Zip(q2 Query,
|
||||
resultSelector func(interface{}, interface{}) interface{}) Query {
|
||||
// resultSelector to corresponding elements of the two collections.
|
||||
// The method returns a collection of the values that are returned by resultSelector.
|
||||
// If the input collections do not have the same number of elements,
|
||||
// the method combines elements until it reaches the end of one of the collections.
|
||||
// For example, if one collection has three elements and the other one has four,
|
||||
// the result collection has only three elements.
|
||||
func (q Query) Zip(
|
||||
q2 Query,
|
||||
resultSelector func(interface{}, interface{}) interface{},
|
||||
) Query {
|
||||
|
||||
return Query{
|
||||
Iterate: func() Iterator {
|
||||
@@ -31,25 +33,3 @@ func (q Query) Zip(q2 Query,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ZipT is the typed version of Zip.
|
||||
//
|
||||
// - resultSelectorFn is of type "func(TFirst,TSecond)TResult"
|
||||
//
|
||||
// NOTE: Zip has better performance than ZipT.
|
||||
func (q Query) ZipT(q2 Query,
|
||||
resultSelectorFn interface{}) Query {
|
||||
resultSelectorGenericFunc, err := newGenericFunc(
|
||||
"ZipT", "resultSelectorFn", resultSelectorFn,
|
||||
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
resultSelectorFunc := func(item1 interface{}, item2 interface{}) interface{} {
|
||||
return resultSelectorGenericFunc.Call(item1, item2)
|
||||
}
|
||||
|
||||
return q.Zip(q2, resultSelectorFunc)
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
# github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible
|
||||
github.com/ahmetalpbalkan/go-linq
|
||||
# github.com/ahmetb/go-linq v2.0.0-rc0+incompatible
|
||||
github.com/ahmetb/go-linq
|
||||
# github.com/go-ini/ini v1.57.0
|
||||
github.com/go-ini/ini
|
||||
# github.com/json-iterator/go v1.1.10
|
||||
|
||||
Reference in New Issue
Block a user