feat(Go-Tool):2020/12/14:添加validator.v8源码解析和使用示例

This commit is contained in:
Huangzj
2020-12-14 15:15:58 +08:00
parent 23d7d6e226
commit fa31b23cca
51 changed files with 4619 additions and 1662 deletions
+1
View File
@@ -3,6 +3,7 @@
<component name="Go" enabled="true" /> <component name="Go" enabled="true" />
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$/vendor/gopkg.in/go-playground/validator.v8" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
@@ -15,7 +15,7 @@
// implementation; the file example_pq_test.go has the complete source. // implementation; the file example_pq_test.go has the complete source.
// //
//read note 拷贝一个代码过来解析.感觉在go里面拷贝代码简单多了,依赖可以直接使用。我在这边使用read note(自定义todo标识)来标识我的解析. //read note 拷贝一个代码过来解析.感觉在go里面拷贝代码简单多了,依赖可以直接使用。我在这边使用read note(自定义todo标识)来标识我的解析.
package SourceAnalysis package sourceAnalysis
import "sort" import "sort"
@@ -9,7 +9,7 @@
// // do something with e.Value // // do something with e.Value
// } // }
// //
package SourceAnalysis package sourceAnalysis
// Element is an element of a linked list. // Element is an element of a linked list.
//read note list中对应的元素,list相当是通过一个链表来链接所有的Element元素. //read note list中对应的元素,list相当是通过一个链表来链接所有的Element元素.
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package ring implements operations on circular lists. // Package ring implements operations on circular lists.
package SourceAnalysis package sourceAnalysis
// A Ring is an element of a circular list, or ring. // 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 // 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)
流程图: ![image](2、结构体缓存获取.jpg)
流程图地址:[地址](https://www.processon.com/view/link/5fd6dabb63768906e6db0d25)
@@ -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
@@ -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.
@@ -0,0 +1,366 @@
Package validator
================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png">[![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-8.18.2-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v8&service=github)](https://coveralls.io/github/go-playground/validator?branch=v8)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
[![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v8?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8)
![License](https://img.shields.io/dub/l/vibe-d.svg)
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.
@@ -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
}
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -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)
)
@@ -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())
}
}
@@ -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 -2
View File
@@ -4,16 +4,18 @@ go 1.13
require ( require (
github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75 // indirect github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75 // indirect
github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/ahmetb/go-linq v2.0.0-rc0+incompatible
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect
github.com/go-ini/ini v1.57.0 github.com/go-ini/ini v1.57.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 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/kardianos/govendor v1.0.9 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.5.1 // indirect github.com/stretchr/testify v1.5.1 // indirect
golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect
golang.org/x/tools v0.0.0-20200429213335-127c98bd7927 // 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/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect
) )
+12
View File
@@ -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/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 h1:8q+6K5jJ2RCTD7v7VxMfuUdHyU22GZi+qMY0hpLTnVc=
github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible/go.mod h1:IBvGwXyZVvXuN8KIbGCK53k+fzCKOmAdYhobMdJXOck= 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 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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 h1:Qwzj3wZQW+Plax5Ntj+GYe07DfGj1OH+aL1nMTMaNow=
github.com/go-ini/ini v1.57.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 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 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc=
github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+2
View File
@@ -66,6 +66,8 @@
2020/12/7 :添加container包使用示例和源码分析 2020/12/7 :添加container包使用示例和源码分析
2020/12/14:添加validator.v8源码解析和使用示例
# mod vendor模式加载包 # mod vendor模式加载包
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到
+1
View File
@@ -2,6 +2,7 @@ sudo: false
language: go language: go
go: go:
- 1.5
- 1.7 - 1.7
before_install: before_install:
+1 -188
View File
@@ -1,191 +1,3 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Ahmet Alp Balkan Copyright 2016 Ahmet Alp Balkan
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@@ -199,3 +11,4 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
+10 -91
View File
@@ -1,21 +1,16 @@
# go-linq [![GoDoc](https://godoc.org/github.com/ahmetalpbalkan/go-linq?status.svg)](https://godoc.org/github.com/ahmetalpbalkan/go-linq) [![Build Status](https://travis-ci.org/ahmetalpbalkan/go-linq.svg?branch=master)](https://travis-ci.org/ahmetalpbalkan/go-linq) [![Coverage Status](https://coveralls.io/repos/github/ahmetalpbalkan/go-linq/badge.svg?branch=master)](https://coveralls.io/github/ahmetalpbalkan/go-linq?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/ahmetalpbalkan/go-linq)](https://goreportcard.com/report/github.com/ahmetalpbalkan/go-linq) # go-linq [![GoDoc](https://godoc.org/github.com/ahmetalpbalkan/go-linq?status.svg)](https://godoc.org/github.com/ahmetalpbalkan/go-linq) [![Build Status](https://travis-ci.org/ahmetalpbalkan/go-linq.svg?branch=master)](https://travis-ci.org/ahmetalpbalkan/go-linq) [![Coverage Status](https://coveralls.io/repos/github/ahmetalpbalkan/go-linq/badge.svg?branch=master)](https://coveralls.io/github/ahmetalpbalkan/go-linq?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/ahmetalpbalkan/go-linq)](https://goreportcard.com/report/github.com/ahmetalpbalkan/go-linq)
A powerful language integrated query (LINQ) library for Go. A powerful language integrated query (LINQ) library for Go.
* Written in vanilla Go, no dependencies! * Written in vanilla Go!
* Complete lazy evaluation with iterator pattern
* Safe for concurrent use * 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 * Supports arrays, slices, maps, strings, channels and custom collections
(collection needs to implement `Iterable` interface and element - `Comparable`
interface)
## Installation ## Installation
$ go get github.com/ahmetalpbalkan/go-linq $ 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_ > :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 > 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, > 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)` `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 ```go
import . "github.com/ahmetalpbalkan/go-linq" import . "github.com/ahmetalpbalkan/go-linq"
type Car struct { type Car struct {
year int id, year int
owner, model string owner, model string
} }
... owners := []string{}
var owners []string
From(cars).Where(func(c interface{}) bool { From(cars).Where(func(c interface{}) bool {
return c.(Car).year >= 2015 return c.(Car).year >= 2015
@@ -53,21 +44,7 @@ From(cars).Where(func(c interface{}) bool {
}).ToSlice(&owners) }).ToSlice(&owners)
``` ```
Or, you can use generic functions, like `WhereT` and `SelectT` to simplify your code **Example: Find the author who has written the most books**
(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**
```go ```go
import . "github.com/ahmetalpbalkan/go-linq" 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 }).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 ```go
type MyQuery Query type MyQuery Query
@@ -120,67 +96,10 @@ func (q MyQuery) GreaterThan(threshold int) Query {
result := MyQuery(Range(1,10)).GreaterThan(5).Results() result := MyQuery(Range(1,10)).GreaterThan(5).Results()
``` ```
## Generic Functions **More examples** can be found in [documentation](https://godoc.org/github.com/ahmetalpbalkan/go-linq).
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).
## Release Notes ## 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) v2.0.0 (2016-09-02)
* IMPORTANT: This release is a BREAKING CHANGE. The old version * IMPORTANT: This release is a BREAKING CHANGE. The old version
+19 -124
View File
@@ -2,15 +2,16 @@ package linq
// Aggregate applies an accumulator function over a sequence. // Aggregate applies an accumulator function over a sequence.
// //
// Aggregate method makes it simple to perform a calculation over a sequence of // Aggregate method makes it simple to perform a calculation over a sequence of values.
// values. This method works by calling f() one time for each element in source // 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 // except the first one. Each time f() is called, Aggregate passes both
// element from the sequence and an aggregated value (as the first argument to // the element from the sequence and an aggregated value (as the first argument to f()).
// f()). The first element of source is used as the initial aggregate value. The // The first element of source is used as the initial aggregate value.
// result of f() replaces the previous aggregated value. // The result of f() replaces the previous aggregated value.
// //
// Aggregate returns the final result of f(). // 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() next := q.Iterate()
result, any := next() result, any := next()
@@ -25,40 +26,21 @@ func (q Query) Aggregate(f func(interface{}, interface{}) interface{}) interface
return result 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 // 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
// NOTE: Aggregate has better performance than AggregateT. // except the first one. Each time f() is called, Aggregate passes both
func (q Query) AggregateT(f interface{}) interface{} { // the element from the sequence and an aggregated value (as the first argument to f()).
fGenericFunc, err := newGenericFunc( // The value of the seed parameter is used as the initial aggregate value.
"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.
// The result of f() replaces the previous aggregated value. // The result of f() replaces the previous aggregated value.
// //
// Aggregate returns the final result of f(). // Aggregate returns the final result of f().
func (q Query) AggregateWithSeed(seed interface{}, func (q Query) AggregateWithSeed(
f func(interface{}, interface{}) interface{}) interface{} { seed interface{},
f func(interface{}, interface{}) interface{},
) interface{} {
next := q.Iterate() next := q.Iterate()
result := seed result := seed
@@ -69,90 +51,3 @@ func (q Query) AggregateWithSeed(seed interface{},
return result 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
View File
@@ -2,8 +2,8 @@ package linq
type comparer func(interface{}, interface{}) int type comparer func(interface{}, interface{}) int
// Comparable is an interface that has to be implemented by a custom collection // Comparable is an interface that has to be implemented by a
// elememts in order to work with linq. // custom collection elememts in order to work with linq.
// //
// Example: // Example:
// func (f foo) CompareTo(c Comparable) int { // func (f foo) CompareTo(c Comparable) int {
+6 -6
View File
@@ -1,7 +1,7 @@
package linq package linq
// Append inserts an item to the end of a collection, so it becomes the last // Append inserts an item to the end of a collection,
// item. // so it becomes the last item.
func (q Query) Append(item interface{}) Query { func (q Query) Append(item interface{}) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
@@ -28,8 +28,8 @@ func (q Query) Append(item interface{}) Query {
// Concat concatenates two collections. // Concat concatenates two collections.
// //
// The Concat method differs from the Union method because the Concat method // 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 all the original elements in the input sequences.
// returns only unique elements. // The Union method returns only unique elements.
func (q Query) Concat(q2 Query) Query { func (q Query) Concat(q2 Query) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 // Prepend inserts an item to the beginning of a collection,
// first item. // so it becomes the first item.
func (q Query) Prepend(item interface{}) Query { func (q Query) Prepend(item interface{}) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
+5 -28
View File
@@ -1,7 +1,7 @@
package linq package linq
// Distinct method returns distinct elements from a collection. The result is an // Distinct method returns distinct elements from a collection.
// unordered collection that contains no duplicate values. // The result is an unordered collection that contains no duplicate values.
func (q Query) Distinct() Query { func (q Query) Distinct() Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
@@ -22,11 +22,11 @@ func (q Query) Distinct() Query {
} }
} }
// Distinct method returns distinct elements from a collection. The result is an // Distinct method returns distinct elements from a collection.
// ordered collection that contains no duplicate values. // The result is an ordered collection that contains no duplicate values.
// //
// NOTE: Distinct method on OrderedQuery type has better performance than // 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 { func (oq OrderedQuery) Distinct() OrderedQuery {
return OrderedQuery{ return OrderedQuery{
orders: oq.orders, 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
View File
@@ -1,6 +1,5 @@
// Package linq provides methods for querying and manipulating slices, arrays, // Package linq provides methods for querying and manipulating
// maps, strings, channels and collections. // slices, arrays, maps, strings, channels and collections.
// //
// Authors: Alexander Kalankhodzhaev (kalan), Ahmet Alp Balkan, Cleiton Marques // Authors: Alexander Kalankhodzhaev (kalan), Ahmet Alp Balkan
// Souza.
package linq package linq
+9 -29
View File
@@ -1,7 +1,8 @@
package linq package linq
// Except produces the set difference of two sequences. The set difference is // Except produces the set difference of two sequences.
// the members of the first sequence that don't appear in the second sequence. // 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 { func (q Query) Except(q2 Query) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 // ExceptBy invokes a transform function on each element of a collection
// produces the set difference of two sequences. The set difference is the // and produces the set difference of two sequences.
// members of the first sequence that don't appear in the second sequence. // The set difference is the members of the first sequence
func (q Query) ExceptBy(q2 Query, // that don't appear in the second sequence.
selector func(interface{}) interface{}) Query { func (q Query) ExceptBy(
q2 Query, selector func(interface{}) interface{}) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
next := q.Iterate() 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
View File
@@ -5,30 +5,30 @@ import "reflect"
// Iterator is an alias for function to iterate over data. // Iterator is an alias for function to iterate over data.
type Iterator func() (item interface{}, ok bool) type Iterator func() (item interface{}, ok bool)
// Query is the type returned from query functions. It can be iterated manually // Query is the type returned from query functions.
// as shown in the example. // It can be iterated manually as shown in the example.
type Query struct { type Query struct {
Iterate func() Iterator Iterate func() Iterator
} }
// KeyValue is a type that is used to iterate over a map (if query is created // KeyValue is a type that is used to iterate over a map
// from a map). This type is also used by ToMap() method to output result of a // (if query is created from a map). This type is also used by
// query into a map. // ToMap() method to output result of a query into a map.
type KeyValue struct { type KeyValue struct {
Key interface{} Key interface{}
Value interface{} Value interface{}
} }
// Iterable is an interface that has to be implemented by a custom collection in // Iterable is an interface that has to be implemented by a
// order to work with linq. // custom collection in order to work with linq.
type Iterable interface { type Iterable interface {
Iterate() Iterator Iterate() Iterator
} }
// From initializes a linq query with passed slice, array or map as the source. // From initializes a linq query with passed slice, array or map
// String, channel or struct implementing Iterable interface can be used as an // as the source. String, channel or struct implementing Iterable
// input. In this case From delegates it to FromString, FromChannel and // interface can be used as an input. In this case From delegates it
// FromIterable internally. // to FromString, FromChannel and FromIterable internally.
func From(source interface{}) Query { func From(source interface{}) Query {
src := reflect.ValueOf(source) src := reflect.ValueOf(source)
@@ -84,8 +84,8 @@ func From(source interface{}) Query {
} }
} }
// FromChannel initializes a linq query with passed channel, linq iterates over // FromChannel initializes a linq query with passed channel,
// channel until it is closed. // linq iterates over channel until it is closed.
func FromChannel(source <-chan interface{}) Query { func FromChannel(source <-chan interface{}) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
@@ -97,8 +97,8 @@ func FromChannel(source <-chan interface{}) Query {
} }
} }
// FromString initializes a linq query with passed string, linq iterates over // FromString initializes a linq query with passed string,
// runes of string. // linq iterates over runes of string.
func FromString(source string) Query { func FromString(source string) Query {
runes := []rune(source) runes := []rune(source)
len := len(runes) len := len(runes)
@@ -120,8 +120,8 @@ func FromString(source string) Query {
} }
} }
// FromIterable initializes a linq query with custom collection passed. This // FromIterable initializes a linq query with custom collection passed.
// collection has to implement Iterable interface, linq iterates over items, // This collection has to implement Iterable interface, linq iterates over items,
// that has to implement Comparable interface or be basic types. // that has to implement Comparable interface or be basic types.
func FromIterable(source Iterable) Query { func FromIterable(source Iterable) Query {
return Query{ return Query{
-138
View File
@@ -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
View File
@@ -6,11 +6,14 @@ type Group struct {
Group []interface{} Group []interface{}
} }
// GroupBy method groups the elements of a collection according to a specified // GroupBy method groups the elements of a collection according
// key selector function and projects the elements for each group by using a // to a specified key selector function and projects the elements for each group
// specified function. // by using a specified function.
func (q Query) GroupBy(keySelector func(interface{}) interface{}, func (q Query) GroupBy(
elementSelector func(interface{}) interface{}) Query { keySelector func(interface{}) interface{},
elementSelector func(interface{}) interface{},
) Query {
return Query{ return Query{
func() Iterator { func() Iterator {
next := q.Iterate() 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
View File
@@ -1,27 +1,25 @@
package linq package linq
import "reflect"
// GroupJoin correlates the elements of two collections based on key equality, // GroupJoin correlates the elements of two collections based on key equality,
// and groups the results. // and groups the results.
// //
// This method produces hierarchical results, which means that elements from // This method produces hierarchical results, which means that elements from outer query
// outer query are paired with collections of matching elements from inner. // are paired with collections of matching elements from inner. GroupJoin enables you
// GroupJoin enables you to base your results on a whole set of matches for each // to base your results on a whole set of matches for each element of outer query.
// element of outer query.
// //
// The resultSelector function is called only one time for each outer element // 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 // together with a collection of all the inner elements that match the outer element.
// element. This differs from the Join method, in which the result selector // This differs from the Join method, in which the result selector function is invoked
// function is invoked on pairs that contain one element from outer and one // on pairs that contain one element from outer and one element from inner.
// element from inner.
// //
// GroupJoin preserves the order of the elements of outer, and for each element // GroupJoin preserves the order of the elements of outer, and for each element of outer,
// of outer, the order of the matching elements from inner. // the order of the matching elements from inner.
func (q Query) GroupJoin(inner Query, func (q Query) GroupJoin(
inner Query,
outerKeySelector func(interface{}) interface{}, outerKeySelector func(interface{}) interface{},
innerKeySelector 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{ return Query{
Iterate: func() Iterator { 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
View File
@@ -2,8 +2,8 @@ package linq
// Intersect produces the set intersection of the source collection and the // 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 // 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 // the set that contains all the elements of A that also appear in B,
// other elements. // but no other elements.
func (q Query) Intersect(q2 Query) Query { func (q Query) Intersect(q2 Query) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 // 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 // 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 // the set that contains all the elements of A that also appear in B,
// other elements. // but no other elements.
// //
// IntersectBy invokes a transform function on each element of both collections. // IntersectBy invokes a transform function on each element of both collections.
func (q Query) IntersectBy(q2 Query, func (q Query) IntersectBy(
selector func(interface{}) interface{}) Query { q2 Query,
selector func(interface{}) interface{},
) Query {
return Query{ return Query{
Iterate: func() Iterator { 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
View File
@@ -2,18 +2,20 @@ package linq
// Join correlates the elements of two collection based on matching keys. // 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 // A join refers to the operation of correlating the elements of two sources
// information based on a common key. Join brings the two information 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 // and the keys by which they are matched together in one method call.
// differs from the use of SelectMany, which requires more than one method call // This differs from the use of SelectMany, which requires more than one method call
// to perform the same operation. // to perform the same operation.
// //
// Join preserves the order of the elements of outer collection, and for each of // Join preserves the order of the elements of outer collection,
// these elements, the order of the matching elements of inner. // and for each of these elements, the order of the matching elements of inner.
func (q Query) Join(inner Query, func (q Query) Join(
inner Query,
outerKeySelector func(interface{}) interface{}, outerKeySelector func(interface{}) interface{},
innerKeySelector 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{ return Query{
Iterate: func() Iterator { 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
View File
@@ -8,17 +8,18 @@ type order struct {
desc bool desc bool
} }
// OrderedQuery is the type returned from OrderBy, OrderByDescending ThenBy and // OrderedQuery is the type returned from OrderBy, OrderByDescending
// ThenByDescending functions. // ThenBy and ThenByDescending functions.
type OrderedQuery struct { type OrderedQuery struct {
Query Query
original Query original Query
orders []order orders []order
} }
// OrderBy sorts the elements of a collection in ascending order. Elements are // OrderBy sorts the elements of a collection in ascending order.
// sorted according to a key. // Elements are sorted according to a key.
func (q Query) OrderBy(selector func(interface{}) interface{}) OrderedQuery { func (q Query) OrderBy(
selector func(interface{}) interface{}) OrderedQuery {
return OrderedQuery{ return OrderedQuery{
orders: []order{{selector: selector}}, orders: []order{{selector: selector}},
original: q, 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. // OrderByDescending sorts the elements of a collection in descending order.
// Elements are sorted according to a key. // 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{ return OrderedQuery{
orders: []order{{selector: selector, desc: true}}, orders: []order{{selector: selector, desc: true}},
original: q, original: q,
@@ -89,28 +70,9 @@ func (q Query) OrderByDescending(selector func(interface{}) interface{}) Ordered
} }
} }
// OrderByDescendingT is the typed version of OrderByDescending. // ThenBy performs a subsequent ordering of the elements in a collection
// - selectorFn is of type "func(TSource) TKey" // in ascending order. This method enables you to specify multiple sort criteria
// NOTE: OrderByDescending has better performance than OrderByDescendingT. // by applying any number of ThenBy or ThenByDescending methods.
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.
func (oq OrderedQuery) ThenBy( func (oq OrderedQuery) ThenBy(
selector func(interface{}) interface{}) OrderedQuery { selector func(interface{}) interface{}) OrderedQuery {
return OrderedQuery{ return OrderedQuery{
@@ -136,29 +98,11 @@ func (oq OrderedQuery) ThenBy(
} }
} }
// ThenByT is the typed version of ThenBy. // ThenByDescending performs a subsequent ordering of the elements in a collection
// - selectorFn is of type "func(TSource) TKey" // in descending order. This method enables you to specify multiple sort criteria
// NOTE: ThenBy has better performance than ThenByT. // by applying any number of ThenBy or ThenByDescending methods.
func (oq OrderedQuery) ThenByT(selectorFn interface{}) OrderedQuery { func (oq OrderedQuery) ThenByDescending(
selectorGenericFunc, err := newGenericFunc( selector func(interface{}) interface{}) OrderedQuery {
"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 {
return OrderedQuery{ return OrderedQuery{
orders: append(oq.orders, order{selector: selector, desc: true}), orders: append(oq.orders, order{selector: selector, desc: true}),
original: oq.original, original: oq.original,
@@ -182,32 +126,10 @@ func (oq OrderedQuery) ThenByDescending(selector func(interface{}) interface{})
} }
} }
// ThenByDescendingT is the typed version of ThenByDescending. // Sort returns a new query by sorting elements with provided less function
// - selectorFn is of type "func(TSource) TKey" // in ascending order. The comparer function should return true if the parameter i
// NOTE: ThenByDescending has better performance than ThenByDescendingT. // is less than j. While this method is uglier than chaining OrderBy, OrderByDescending,
func (oq OrderedQuery) ThenByDescendingT(selectorFn interface{}) OrderedQuery { // ThenBy and ThenByDescending methods, it's performance is much better.
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.
func (q Query) Sort(less func(i, j interface{}) bool) Query { func (q Query) Sort(less func(i, j interface{}) bool) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 { type sorter struct {
items []interface{} items []interface{}
less func(i, j interface{}) bool less func(i, j interface{}) bool
@@ -270,10 +173,6 @@ func (q Query) sort(orders []order) (r []interface{}) {
r = append(r, item) r = append(r, item)
} }
if len(r) == 0 {
return
}
for i, j := range orders { for i, j := range orders {
orders[i].compare = getComparer(j.selector(r[0])) orders[i].compare = getComparer(j.selector(r[0]))
} }
+33 -297
View File
@@ -18,27 +18,6 @@ func (q Query) All(predicate func(interface{}) bool) bool {
return true 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. // Any determines whether any element of a collection exists.
func (q Query) Any() bool { func (q Query) Any() bool {
_, ok := q.Iterate()() _, ok := q.Iterate()()
@@ -58,28 +37,6 @@ func (q Query) AnyWith(predicate func(interface{}) bool) bool {
return false 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. // Average computes the average of a collection of numeric values.
func (q Query) Average() (r float64) { func (q Query) Average() (r float64) {
next := q.Iterate() next := q.Iterate()
@@ -147,8 +104,8 @@ func (q Query) Count() (r int) {
return return
} }
// CountWith returns a number that represents how many elements in the specified // CountWith returns a number that represents how many elements
// collection satisfy a condition. // in the specified collection satisfy a condition.
func (q Query) CountWith(predicate func(interface{}) bool) (r int) { func (q Query) CountWith(predicate func(interface{}) bool) (r int) {
next := q.Iterate() next := q.Iterate()
@@ -161,36 +118,14 @@ func (q Query) CountWith(predicate func(interface{}) bool) (r int) {
return 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. // First returns the first element of a collection.
func (q Query) First() interface{} { func (q Query) First() interface{} {
item, _ := q.Iterate()() item, _ := q.Iterate()()
return item return item
} }
// FirstWith returns the first element of a collection that satisfies a // FirstWith returns the first element of a collection that satisfies
// specified condition. // a specified condition.
func (q Query) FirstWith(predicate func(interface{}) bool) interface{} { func (q Query) FirstWith(predicate func(interface{}) bool) interface{} {
next := q.Iterate() next := q.Iterate()
@@ -203,99 +138,6 @@ func (q Query) FirstWith(predicate func(interface{}) bool) interface{} {
return nil 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. // Last returns the last element of a collection.
func (q Query) Last() (r interface{}) { func (q Query) Last() (r interface{}) {
next := q.Iterate() next := q.Iterate()
@@ -307,8 +149,8 @@ func (q Query) Last() (r interface{}) {
return return
} }
// LastWith returns the last element of a collection that satisfies a specified // LastWith returns the last element of a collection that satisfies
// condition. // a specified condition.
func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) { func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) {
next := q.Iterate() next := q.Iterate()
@@ -321,28 +163,6 @@ func (q Query) LastWith(predicate func(interface{}) bool) (r interface{}) {
return 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. // Max returns the maximum value in a collection of values.
func (q Query) Max() (r interface{}) { func (q Query) Max() (r interface{}) {
next := q.Iterate() next := q.Iterate()
@@ -410,8 +230,8 @@ func (q Query) SequenceEqual(q2 Query) bool {
return !ok2 return !ok2
} }
// Single returns the only element of a collection, and nil if there is not // Single returns the only element of a collection, and nil
// exactly one element in the collection. // if there is not exactly one element in the collection.
func (q Query) Single() interface{} { func (q Query) Single() interface{} {
next := q.Iterate() next := q.Iterate()
item, ok := next() item, ok := next()
@@ -427,8 +247,8 @@ func (q Query) Single() interface{} {
return item return item
} }
// SingleWith returns the only element of a collection that satisfies a // SingleWith returns the only element of a collection that satisfies
// specified condition, and nil if more than one such element exists. // a specified condition, and nil if more than one such element exists.
func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) { func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) {
next := q.Iterate() next := q.Iterate()
found := false found := false
@@ -447,31 +267,10 @@ func (q Query) SingleWith(predicate func(interface{}) bool) (r interface{}) {
return 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. // SumInts computes the sum of a collection of numeric values.
// //
// Values can be of any integer type: int, int8, int16, int32, int64. The result // Values can be of any integer type: int, int8, int16, int32, int64.
// is int64. Method returns zero if collection contains no elements. // The result is int64. Method returns zero if collection contains no elements.
func (q Query) SumInts() (r int64) { func (q Query) SumInts() (r int64) {
next := q.Iterate() next := q.Iterate()
item, ok := next() item, ok := next()
@@ -491,9 +290,8 @@ func (q Query) SumInts() (r int64) {
// SumUInts computes the sum of a collection of numeric values. // SumUInts computes the sum of a collection of numeric values.
// //
// Values can be of any unsigned integer type: uint, uint8, uint16, uint32, // Values can be of any unsigned integer type: uint, uint8, uint16, uint32, uint64.
// uint64. The result is uint64. Method returns zero if collection contains no // The result is uint64. Method returns zero if collection contains no elements.
// elements.
func (q Query) SumUInts() (r uint64) { func (q Query) SumUInts() (r uint64) {
next := q.Iterate() next := q.Iterate()
item, ok := next() item, ok := next()
@@ -532,8 +330,8 @@ func (q Query) SumFloats() (r float64) {
return return
} }
// ToChannel iterates over a collection and outputs each element to a channel, // ToChannel iterates over a collection and outputs each element
// then closes it. // to a channel, then closes it.
func (q Query) ToChannel(result chan<- interface{}) { func (q Query) ToChannel(result chan<- interface{}) {
next := q.Iterate() 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. // ToMap iterates over a collection and populates result map with elements.
// Collection elements have to be of KeyValue type to use this method. To // Collection elements have to be of KeyValue type to use this method.
// populate a map with elements of different type use ToMapBy method. ToMap // To populate a map with elements of different type use ToMapBy method.
// doesn't empty the result map before populating it.
func (q Query) ToMap(result interface{}) { func (q Query) ToMap(result interface{}) {
q.ToMapBy( q.ToMapBy(
result, result,
@@ -559,14 +356,15 @@ func (q Query) ToMap(result interface{}) {
}) })
} }
// ToMapBy iterates over a collection and populates the result map with // ToMapBy iterates over a collection and populates result map with elements.
// elements. Functions keySelector and valueSelector are executed for each // Functions keySelector and valueSelector are executed for each element of the collection
// element of the collection to generate key and value for the map. Generated // to generate key and value for the map. Generated key and value types must be assignable
// key and value types must be assignable to the map's key and value types. // to the map's key and value types.
// ToMapBy doesn't empty the result map before populating it. func (q Query) ToMapBy(
func (q Query) ToMapBy(result interface{}, result interface{},
keySelector func(interface{}) interface{}, keySelector func(interface{}) interface{},
valueSelector func(interface{}) interface{}) { valueSelector func(interface{}) interface{},
) {
res := reflect.ValueOf(result) res := reflect.ValueOf(result)
m := reflect.Indirect(res) m := reflect.Indirect(res)
next := q.Iterate() next := q.Iterate()
@@ -581,78 +379,16 @@ func (q Query) ToMapBy(result interface{},
res.Elem().Set(m) res.Elem().Set(m)
} }
// ToMapByT is the typed version of ToMapBy. // ToSlice iterates over a collection and populates result slice with elements.
// // Collection elements must be assignable to the slice's element type.
// - keySelectorFn is of type "func(TSource)TKey" func (q Query) ToSlice(result interface{}) {
// - valueSelectorFn is of type "func(TSource)TValue" res := reflect.ValueOf(result)
//
// 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)
slice := reflect.Indirect(res) 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() next := q.Iterate()
index := 0
for item, ok := next(); ok; item, ok = next() { for item, ok := next(); ok; item, ok = next() {
if index >= cap { slice = reflect.Append(slice, reflect.ValueOf(item))
slice, cap = grow(slice)
}
slice.Index(index).Set(reflect.ValueOf(item))
index++
} }
// reslice the len(res)==cap(res) actual res size res.Elem().Set(slice)
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
} }
+3 -3
View File
@@ -2,9 +2,9 @@ package linq
// Reverse inverts the order of the elements in a collection. // Reverse inverts the order of the elements in a collection.
// //
// Unlike OrderBy, this sorting method does not consider the actual values // Unlike OrderBy, this sorting method does not consider the actual values themselves
// themselves in determining the order. Rather, it just returns the elements in // in determining the order. Rather, it just returns the elements in the reverse order
// the reverse order from which they are produced by the underlying source. // from which they are produced by the underlying source.
func (q Query) Reverse() Query { func (q Query) Reverse() Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
+27 -65
View File
@@ -1,16 +1,17 @@
package linq package linq
// Select projects each element of a collection into a new form. Returns a query // Select projects each element of a collection into a new form.
// with the result of invoking the transform function on each element of // Returns a query with the result of invoking the transform function
// original source. // on each element of original source.
// //
// This projection method requires the transform function, selector, to produce // This projection method requires the transform function, selector,
// one value for each value in the source collection. If selector returns a // to produce one value for each value in the source collection.
// value that is itself a collection, it is up to the consumer to traverse the // If selector returns a value that is itself a collection,
// subcollections manually. In such a situation, it might be better for your // it is up to the consumer to traverse the subcollections manually.
// query to return a single coalesced collection of values. To achieve this, use // In such a situation, it might be better for your query to return a single
// the SelectMany method instead of Select. Although SelectMany works similarly // coalesced collection of values. To achieve this, use the SelectMany method
// to Select, it differs in that the transform function returns a collection // 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. // that is then expanded by SelectMany before it is returned.
func (q Query) Select(selector func(interface{}) interface{}) Query { func (q Query) Select(selector func(interface{}) interface{}) Query {
return Query{ return Query{
@@ -30,44 +31,24 @@ func (q Query) Select(selector func(interface{}) interface{}) Query {
} }
} }
// SelectT is the typed version of Select. // SelectIndexed projects each element of a collection into a new form
// - selectorFn is of type "func(TSource)TResult" // by incorporating the element's index. Returns a query with the result
// NOTE: Select has better performance than SelectT. // of invoking the transform function on each element of original source.
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.
// //
// The first argument to selector represents the zero-based index of that // The first argument to selector represents the zero-based index of that element
// element in the source collection. This can be useful if the elements are in a // in the source collection. This can be useful if the elements are in a known order
// known order and you want to do something with an element at a particular // and you want to do something with an element at a particular index,
// index, for example. It can also be useful if you want to retrieve the index // for example. It can also be useful if you want to retrieve the index of one
// of one or more elements. The second argument to selector represents the // or more elements. The second argument to selector represents the element to process.
// element to process.
// //
// This projection method requires the transform function, selector, to produce // This projection method requires the transform function, selector,
// one value for each value in the source collection. If selector returns a // to produce one value for each value in the source collection.
// value that is itself a collection, it is up to the consumer to traverse the // If selector returns a value that is itself a collection,
// subcollections manually. In such a situation, it might be better for your // it is up to the consumer to traverse the subcollections manually.
// query to return a single coalesced collection of values. To achieve this, use // In such a situation, it might be better for your query to return a single
// the SelectMany method instead of Select. Although SelectMany works similarly // coalesced collection of values. To achieve this, use the SelectMany method
// to Select, it differs in that the transform function returns a collection // 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. // that is then expanded by SelectMany before it is returned.
func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query { func (q Query) SelectIndexed(selector func(int, interface{}) interface{}) Query {
return 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
View File
@@ -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" // 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
// NOTE: SelectMany has better performance than SelectManyT. // and you want to do something with an element at a particular index, for example.
func (q Query) SelectManyT(selectorFn interface{}) Query { // 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.
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.
func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query { func (q Query) SelectManyIndexed(selector func(int, interface{}) Query) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 // SelectManyBy projects each element of a collection to a Query, iterates and
// flattens the resulting collection into one collection, and invokes a result // flattens the resulting collection into one collection, and invokes
// selector function on each element therein. // a result selector function on each element therein.
func (q Query) SelectManyBy(selector func(interface{}) Query, func (q Query) SelectManyBy(
resultSelector func(interface{}, interface{}) interface{}) Query { selector func(interface{}) Query,
resultSelector func(interface{}, interface{}) interface{},
) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
@@ -146,53 +103,18 @@ func (q Query) SelectManyBy(selector func(interface{}) Query,
} }
} }
item = resultSelector(item, outer) item = resultSelector(outer, item)
return return
} }
}, },
} }
} }
// SelectManyByT is the typed version of SelectManyBy. // SelectManyByIndexed projects each element of a collection to a Query, iterates and
// // flattens the resulting collection into one collection, and invokes
// - selectorFn is of type "func(TSource)Query" // a result selector function on each element therein.
// - resultSelectorFn is of type "func(TSource,TCollection)TResult" // The index of each source element is used in the intermediate projected form
// // of that element.
// 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.
func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query, func (q Query) SelectManyByIndexed(selector func(int, interface{}) Query,
resultSelector func(interface{}, interface{}) 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 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
View File
@@ -1,7 +1,7 @@
package linq package linq
// Skip bypasses a specified number of elements in a collection and then returns // Skip bypasses a specified number of elements in a collection
// the remaining elements. // and then returns the remaining elements.
func (q Query) Skip(count int) Query { func (q Query) Skip(count int) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 // SkipWhile bypasses elements in a collection as long as a specified condition is true
// is true and then returns the remaining elements. // and then returns the remaining elements.
// //
// This method tests each element by using predicate and skips the element if // This method tests each element by using predicate and skips the element
// the result is true. After the predicate function returns false for an // if the result is true. After the predicate function returns false for an element,
// element, that element and the remaining elements in source are returned and // that element and the remaining elements in source are returned
// there are no more invocations of predicate. // and there are no more invocations of predicate.
func (q Query) SkipWhile(predicate func(interface{}) bool) Query { func (q Query) SkipWhile(predicate func(interface{}) bool) Query {
return Query{ return Query{
Iterate: func() Iterator { 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" // 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,
// NOTE: SkipWhile has better performance than SkipWhileT. // that element and the remaining elements in source are returned
func (q Query) SkipWhileT(predicateFn interface{}) Query { // and there are no more invocations of predicate.
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.
func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query { func (q Query) SkipWhileIndexed(predicate func(int, interface{}) bool) Query {
return Query{ return Query{
Iterate: func() Iterator { 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
View File
@@ -1,7 +1,6 @@
package linq package linq
// Take returns a specified number of contiguous elements from the start of a // Take returns a specified number of contiguous elements from the start of a collection.
// collection.
func (q Query) Take(count int) Query { func (q Query) Take(count int) Query {
return Query{ return Query{
Iterate: func() Iterator { 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 // TakeWhile returns elements from a collection as long as a specified condition is true,
// is true, and then skips the remaining elements. // and then skips the remaining elements.
func (q Query) TakeWhile(predicate func(interface{}) bool) Query { func (q Query) TakeWhile(predicate func(interface{}) bool) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
@@ -50,33 +49,10 @@ func (q Query) TakeWhile(predicate func(interface{}) bool) Query {
} }
} }
// TakeWhileT is the typed version of TakeWhile. // 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.
// - predicateFn is of type "func(TSource)bool" // The first argument of predicate represents the zero-based index of the element
// // within collection. The second argument represents the element to test.
// 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.
func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query { func (q Query) TakeWhileIndexed(predicate func(int, interface{}) bool) Query {
return Query{ return Query{
Iterate: func() Iterator { 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
View File
@@ -2,9 +2,10 @@ package linq
// Union produces the set union of two collections. // Union produces the set union of two collections.
// //
// This method excludes duplicates from the return set. This is different // This method excludes duplicates from the return set.
// behavior to the Concat method, which returns all the elements in the input // This is different behavior to the Concat method,
// collection including duplicates. // which returns all the elements in the input collection
// including duplicates.
func (q Query) Union(q2 Query) Query { func (q Query) Union(q2 Query) Query {
return Query{ return Query{
Iterate: func() Iterator { Iterate: func() Iterator {
+4 -47
View File
@@ -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" // The first argument represents the zero-based index of the element within collection.
// // The second argument of predicate represents the element to test.
// 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.
func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query { func (q Query) WhereIndexed(predicate func(int, interface{}) bool) Query {
return Query{ return Query{
Iterate: func() Iterator { 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
View File
@@ -1,17 +1,19 @@
package linq package linq
// Zip applies a specified function to the corresponding elements of two // Zip applies a specified function to the corresponding elements
// collections, producing a collection of the results. // of two collections, producing a collection of the results.
// //
// The method steps through the two input collections, applying function // The method steps through the two input collections, applying function
// resultSelector to corresponding elements of the two collections. The method // resultSelector to corresponding elements of the two collections.
// returns a collection of the values that are returned by resultSelector. If // The method returns a collection of the values that are returned by resultSelector.
// the input collections do not have the same number of elements, the method // If the input collections do not have the same number of elements,
// combines elements until it reaches the end of one of the collections. For // the method combines elements until it reaches the end of one of the collections.
// example, if one collection has three elements and the other one has four, the // For example, if one collection has three elements and the other one has four,
// result collection has only three elements. // the result collection has only three elements.
func (q Query) Zip(q2 Query, func (q Query) Zip(
resultSelector func(interface{}, interface{}) interface{}) Query { q2 Query,
resultSelector func(interface{}, interface{}) interface{},
) Query {
return Query{ return Query{
Iterate: func() Iterator { 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)
}
+2 -2
View File
@@ -1,5 +1,5 @@
# github.com/ahmetalpbalkan/go-linq v2.0.0-rc0+incompatible # github.com/ahmetb/go-linq v2.0.0-rc0+incompatible
github.com/ahmetalpbalkan/go-linq github.com/ahmetb/go-linq
# github.com/go-ini/ini v1.57.0 # github.com/go-ini/ini v1.57.0
github.com/go-ini/ini github.com/go-ini/ini
# github.com/json-iterator/go v1.1.10 # github.com/json-iterator/go v1.1.10