Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(binding): use internal tagexpr
Browse files Browse the repository at this point in the history
* remove unused code in github.com/bytedance/go-tagexpr
* remove personal repos github.com/henrylee2cn/*
xiaost committed Nov 20, 2024

Verified

This commit was signed with the committer’s verified signature.
paescuj Pascal Jufer
1 parent 6835f14 commit a607728
Showing 29 changed files with 518 additions and 586 deletions.
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -3,12 +3,12 @@ module github.com/cloudwego/hertz
go 1.17

require (
github.com/bytedance/go-tagexpr/v2 v2.9.2
github.com/bytedance/gopkg v0.1.0
github.com/bytedance/mockey v1.2.12
github.com/bytedance/sonic v1.12.0
github.com/cloudwego/netpoll v0.6.4
github.com/fsnotify/fsnotify v1.5.4
github.com/nyaruka/phonenumbers v1.0.55
github.com/tidwall/gjson v1.14.4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.24.0
@@ -21,11 +21,8 @@ require (
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/henrylee2cn/ameda v1.4.10 // indirect
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/nyaruka/phonenumbers v1.0.55 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
14 changes: 0 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
github.com/bytedance/go-tagexpr/v2 v2.9.2 h1:QySJaAIQgOEDQBLS3x9BxOWrnhqu5sQ+f6HaZIxD39I=
github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM=
github.com/bytedance/gopkg v0.0.0-20240507064146-197ded923ae3/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
github.com/bytedance/gopkg v0.1.0 h1:aAxB7mm1qms4Wz4sp8e1AtKDOeFLtdqvGiUe7aonRJs=
github.com/bytedance/gopkg v0.1.0/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
github.com/bytedance/mockey v1.2.12 h1:aeszOmGw8CPX8CRx1DZ/Glzb1yXvhjDh6jdFBNZjsU4=
@@ -14,8 +11,6 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cloudwego/netpoll v0.6.2 h1:+KdILv5ATJU+222wNNXpHapYaBeRvvL8qhJyhcxRxrQ=
github.com/cloudwego/netpoll v0.6.2/go.mod h1:kaqvfZ70qd4T2WtIIpCOi5Cxyob8viEpzLhCrTrz3HM=
github.com/cloudwego/netpoll v0.6.4 h1:z/dA4sOTUQof6zZIO4QNnLBXsDFFFEos9OOGloR6kno=
github.com/cloudwego/netpoll v0.6.4/go.mod h1:BtM+GjKTdwKoC8IOzD08/+8eEn2gYoiNLipFca6BVXQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -30,11 +25,6 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
github.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQyCAk=
github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 h1:yE9ULgp02BhYIrO6sdV/FPe0xQM6fNHkVQW2IAymfM0=
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
@@ -51,14 +41,11 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -92,7 +79,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
204 changes: 2 additions & 202 deletions internal/tagexpr/README.md
Original file line number Diff line number Diff line change
@@ -1,203 +1,3 @@
# go-tagexpr [![report card](https://goreportcard.com/badge/github.com/bytedance/go-tagexpr?style=flat-square)](http://goreportcard.com/report/bytedance/go-tagexpr) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/bytedance/go-tagexpr)
# go-tagexpr

An interesting go struct tag expression syntax for field validation, etc.

## Usage

- **[Validator](https://github.com/bytedance/go-tagexpr/tree/master/validator)**: A powerful validator that supports struct tag expression

- **[Binding](https://github.com/bytedance/go-tagexpr/tree/master/binding)**: A powerful HTTP request parameters binder that supports struct tag expression

## Feature

- Support for a variety of common operator
- Support for accessing arrays, slices, members of the dictionary
- Support access to any field in the current structure
- Support access to nested fields, non-exported fields, etc.
- Support variable
- Support registers function expression
- Built-in len, sprintf, regexp functions
- Support single mode and multiple mode to define expression
- Parameter check subpackage
- Use offset pointers to directly take values, better performance
- Required go version ≥1.9

## Example

```go
package tagexpr_test

import (
"fmt"

tagexpr "github.com/bytedance/go-tagexpr/v2"
)

func Example() {
type T struct {
A int `tagexpr:"$<0||$>=100"`
B string `tagexpr:"len($)>1 && regexp('^\\w*$')"`
C bool `tagexpr:"expr1:(f.g)$>0 && $; expr2:'C must be true when T.f.g>0'"`
d []string `tagexpr:"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)"`
e map[string]int `tagexpr:"len($)==$['len']"`
e2 map[string]*int `tagexpr:"len($)==$['len']"`
f struct {
g int `tagexpr:"$"`
}
h int `tagexpr:"$>minVal"`
}

vm := tagexpr.New("tagexpr")
t := &T{
A: 107,
B: "abc",
C: true,
d: []string{"x", "y"},
e: map[string]int{"len": 1},
e2: map[string]*int{"len": new(int)},
f: struct {
g int `tagexpr:"$"`
}{1},
h: 10,
}

tagExpr, err := vm.Run(t)
if err != nil {
panic(err)
}

fmt.Println(tagExpr.Eval("A"))
fmt.Println(tagExpr.Eval("B"))
fmt.Println(tagExpr.Eval("C@expr1"))
fmt.Println(tagExpr.Eval("C@expr2"))
if !tagExpr.Eval("d").(bool) {
fmt.Println(tagExpr.Eval("d@msg"))
}
fmt.Println(tagExpr.Eval("e"))
fmt.Println(tagExpr.Eval("e2"))
fmt.Println(tagExpr.Eval("f.g"))
fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 9}))
fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 11}))

// Output:
// true
// true
// true
// C must be true when T.f.g>0
// invalid d: [x y]
// true
// false
// 1
// true
// false
}
```

## Syntax

Struct tag syntax spec:

```
type T struct {
// Single model
Field1 T1 `tagName:"expression"`
// Multiple model
Field2 T2 `tagName:"exprName:expression; [exprName2:expression2;]..."`
// Omit it
Field3 T3 `tagName:"-"`
// Omit it when it is nil
Field4 T4 `tagName:"?"`
...
}
```

NOTE: **The `exprName` under the same struct field cannot be the same!**

|Operator or Operand|Explain|
|-----|---------|
|`true` `false`|boolean|
|`0` `0.0`|float64 "0"|
|`''`|String|
|`\\'`| Escape `'` delims in string|
|`\"`| Escape `"` delims in string|
|`nil`|nil, undefined|
|`!`|not|
|`+`|Digital addition or string splicing|
|`-`|Digital subtraction or negative|
|`*`|Digital multiplication|
|`/`|Digital division|
|`%`|division remainder, as: `float64(int64(a)%int64(b))`|
|`==`|`eq`|
|`!=`|`ne`|
|`>`|`gt`|
|`>=`|`ge`|
|`<`|`lt`|
|`<=`|`le`|
|`&&`|Logic `and`|
|`\|\|`|Logic `or`|
|`()`|Expression group|
|`(X)$`|Struct field value named X|
|`(X.Y)$`|Struct field value named X.Y|
|`$`|Shorthand for `(X)$`, omit `(X)` to indicate current struct field value|
|`(X)$['A']`|Map value with key A or struct A sub-field in the struct field X|
|`(X)$[0]`|The 0th element or sub-field of the struct field X(type: map, slice, array, struct)|
|`len((X)$)`|Built-in function `len`, the length of struct field X|
|`mblen((X)$)`|the length of string field X (character number)|
|`regexp('^\\w*$', (X)$)`|Regular match the struct field X, return boolean|
|`regexp('^\\w*$')`|Regular match the current struct field, return boolean|
|`sprintf('X value: %v', (X)$)`|`fmt.Sprintf`, format the value of struct field X|
|`range(KvExpr, forEachExpr)`|Iterate over an array, slice, or dictionary <br> - `#k` is the element key var <br> - `#v` is the element value var <br> - `##` is the number of elements <br> - e.g. [example](spec_range_test.go)|
|`in((X)$, enum_1, ...enum_n)`|Check if the first parameter is one of the enumerated parameters|

<!-- |`(X)$k`|Traverse each element key of the struct field X(type: map, slice, array)|
|`(X)$v`|Traverse each element value of the struct field X(type: map, slice, array)| -->

<!-- |`&`|Integer bitwise `and`|
|`\|`|Integer bitwise `or`|
|`^`|Integer bitwise `not` or `xor`|
|`&^`|Integer bitwise `clean`|
|`<<`|Integer bitwise `shift left`|
|`>>`|Integer bitwise `shift right`| -->

Operator priority(high -> low):

* `()` `!` `bool` `float64` `string` `nil`
* `*` `/` `%`
* `+` `-`
* `<` `<=` `>` `>=`
* `==` `!=`
* `&&`
* `||`

## Field Selector

```
field_lv1.field_lv2...field_lvn
```

## Expression Selector

- If expression is **single model** or exprName is `@`:

```
field_lv1.field_lv2...field_lvn
```

- If expression is **multiple model** and exprName is not `@`:

```
field_lv1.field_lv2...field_lvn@exprName
```

## Benchmark

```
goos: darwin
goarch: amd64
pkg: github.com/bytedance/go-tagexpr
BenchmarkTagExpr-4 10000000 148 ns/op 32 B/op 3 allocs/op
BenchmarkReflect-4 10000000 182 ns/op 16 B/op 2 allocs/op
PASS
```

[Go to test code](https://github.com/bytedance/go-tagexpr/blob/master/tagexpr_test.go#L9-L56)
originally from https://github.com/bytedance/go-tagexpr
4 changes: 2 additions & 2 deletions internal/tagexpr/example_test.go
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ package tagexpr_test
import (
"fmt"

tagexpr "github.com/bytedance/go-tagexpr/v2"
"github.com/cloudwego/hertz/internal/tagexpr"
)

func Example() {
@@ -31,7 +31,7 @@ func Example() {
f struct {
g int `tagexpr:"$"`
}
h int `tagexpr:"$>minVal"`
h int `tagexpr:"$>minVal"`
}

vm := tagexpr.New("tagexpr")
46 changes: 2 additions & 44 deletions internal/tagexpr/expr.go
Original file line number Diff line number Diff line change
@@ -15,15 +15,12 @@
package tagexpr

import (
"bytes"
"context"
"fmt"
"os"

"github.com/andeya/goutil"
)

type variableKeyType string

const variableKey variableKeyType = "__ENV_KEY__"

// Expr expression
@@ -45,6 +42,7 @@ func parseExpr(expr string) (*Expr, error) {
sortPriority(e)
return p, nil
}

func (p *Expr) parseExprNode(expr *string, e ExprNode) error {
trimLeftSpace(expr)
if *expr == "" {
@@ -297,43 +295,3 @@ func (eb *exprBackground) SetRightOperand(right ExprNode) {
}

func (*exprBackground) Run(context.Context, string, *TagExpr) interface{} { return nil }

var debugSwitch = goutil.IsGoTest()

func printf(format string, a ...interface{}) {
if debugSwitch {
fmt.Fprintf(os.Stderr, format, a...)
}
}

func printExprNode(node ExprNode) {
if node == nil {
return
}
tail := true
if node.Parent() != nil {
tail = node == node.Parent().RightOperand()
}
printf("%s\n\n", formatExprNode(node, 0, tail))
}

func formatExprNode(node ExprNode, level int, tail bool) []byte {
var b bytes.Buffer
if node == nil {
} else {
b.Write(formatExprNode(node.LeftOperand(), level+1, false))

b.Write(bytes.Repeat([]byte(" "), level))
if tail {
b.Write([]byte("└── "))
} else {
b.Write([]byte("┌── "))
}

b.Write([]byte(node.String()))
b.Write([]byte("\n"))

b.Write(formatExprNode(node.RightOperand(), level+1, true))
}
return b.Bytes()
}
10 changes: 5 additions & 5 deletions internal/tagexpr/expr_test.go
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ import (
)

func TestExpr(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
val interface{}
}{
@@ -132,7 +132,7 @@ func TestExpr(t *testing.T) {
}

func TestExprWithEnv(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
val interface{}
}{
@@ -162,7 +162,7 @@ func TestExprWithEnv(t *testing.T) {
}

func TestPriority(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
val interface{}
}{
@@ -190,7 +190,7 @@ func TestPriority(t *testing.T) {
}

func TestBuiltInFunc(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
val interface{}
}{
@@ -226,7 +226,7 @@ func TestBuiltInFunc(t *testing.T) {
}

func TestSyntaxIncorrect(t *testing.T) {
var cases = []struct {
cases := []struct {
incorrectExpr string
}{
{incorrectExpr: "1 + + 'a'"},
14 changes: 0 additions & 14 deletions internal/tagexpr/go.mod

This file was deleted.

35 changes: 0 additions & 35 deletions internal/tagexpr/go.sum

This file was deleted.

29 changes: 24 additions & 5 deletions internal/tagexpr/handler.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr

import "reflect"
@@ -29,7 +43,8 @@ func (f *FieldHandler) FieldSelector() FieldSelector {

// Value returns the field value.
// NOTE:
// If initZero==true, initialize nil pointer to zero value
//
// If initZero==true, initialize nil pointer to zero value
func (f *FieldHandler) Value(initZero bool) reflect.Value {
return f.field.reflectValueGetter(f.expr.ptr, initZero)
}
@@ -100,30 +115,34 @@ func (e *ExprHandler) Path() string {

// Eval evaluate the value of the struct tag expression.
// NOTE:
// result types: float64, string, bool, nil
//
// result types: float64, string, bool, nil
func (e *ExprHandler) Eval() interface{} {
return e.expr.s.exprs[e.selector].run(e.base, e.targetExpr)
}

// EvalFloat evaluates the value of the struct tag expression.
// NOTE:
// If the expression value type is not float64, return 0.
//
// If the expression value type is not float64, return 0.
func (e *ExprHandler) EvalFloat() float64 {
r, _ := e.Eval().(float64)
return r
}

// EvalString evaluates the value of the struct tag expression.
// NOTE:
// If the expression value type is not string, return "".
//
// If the expression value type is not string, return "".
func (e *ExprHandler) EvalString() string {
r, _ := e.Eval().(string)
return r
}

// EvalBool evaluates the value of the struct tag expression.
// NOTE:
// If the expression value is not 0, '' or nil, return true.
//
// If the expression value is not 0, '' or nil, return true.
func (e *ExprHandler) EvalBool() bool {
return FakeBool(e.Eval())
}
28 changes: 14 additions & 14 deletions internal/tagexpr/selector.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr

import (
@@ -18,11 +32,6 @@ const (
// FieldSelector expression selector
type FieldSelector string

// JoinFieldSelector creates a field selector.
func JoinFieldSelector(path ...string) string {
return strings.Join(path, FieldSeparator)
}

// Name returns the current field name.
func (f FieldSelector) Name() string {
s := string(f)
@@ -59,15 +68,6 @@ func (f FieldSelector) String() string {
return string(f)
}

// JoinExprSelector creates a expression selector.
func JoinExprSelector(pathFields []string, exprName string) string {
p := strings.Join(pathFields, FieldSeparator)
if p == "" || exprName == "" {
return p
}
return p + ExprNameSeparator + exprName
}

// ExprSelector expression selector
type ExprSelector string

24 changes: 20 additions & 4 deletions internal/tagexpr/selector_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestExprSelector(t *testing.T) {
es := ExprSelector("F1.Index")
field, ok := es.ParentField()
assert.True(t, ok)
assert.Equal(t, "F1", field)
if !ok {
t.Fatal("not ok")
}
if "F1" != field {
t.Fatal(field)
}
}
6 changes: 2 additions & 4 deletions internal/tagexpr/spec_func.go
Original file line number Diff line number Diff line change
@@ -20,8 +20,6 @@ import (
"reflect"
"regexp"
"strings"

"github.com/andeya/goutil/errors"
)

// --------------------------- Custom function ---------------------------
@@ -54,7 +52,7 @@ func RegFunc(funcName string, fn func(...interface{}) interface{}, force ...bool
if len(force) == 0 || !force[0] {
_, ok := funcList[funcName]
if ok {
return errors.Errorf("duplicate registration expression function: %s", funcName)
return fmt.Errorf("duplicate registration expression function: %s", funcName)
}
}
funcList[funcName] = newFunc(funcName, fn)
@@ -241,7 +239,7 @@ func readRegexpFuncExprNode(p *Expr, expr *string) ExprNode {
return nil
}
} else {
var currFieldVal = "$"
currFieldVal := "$"
p.parseExprNode(&currFieldVal, operand)
}
trimLeftSpace(subExprNode)
20 changes: 12 additions & 8 deletions internal/tagexpr/spec_func_test.go
Original file line number Diff line number Diff line change
@@ -15,15 +15,15 @@
package tagexpr_test

import (
"reflect"
"regexp"
"testing"

"github.com/bytedance/go-tagexpr/v2"
"github.com/stretchr/testify/assert"
"github.com/cloudwego/hertz/internal/tagexpr"
)

func TestFunc(t *testing.T) {
var emailRegexp = regexp.MustCompile(
emailRegexp := regexp.MustCompile(
"^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$",
)
tagexpr.RegFunc("email", func(args ...interface{}) interface{} {
@@ -38,12 +38,12 @@ func TestFunc(t *testing.T) {
return emailRegexp.MatchString(s)
})

var vm = tagexpr.New("te")
vm := tagexpr.New("te")

type T struct {
Email string `te:"email($)"`
}
var cases = []struct {
cases := []struct {
email string
expect bool
}{
@@ -65,7 +65,7 @@ func TestFunc(t *testing.T) {
type R struct {
Str string `vd:"mblen($)<6"`
}
var lenCases = []struct {
lenCases := []struct {
str string
expect bool
}{
@@ -87,7 +87,7 @@ func TestFunc(t *testing.T) {
}

func TestRangeIn(t *testing.T) {
var vm = tagexpr.New("te")
vm := tagexpr.New("te")
type S struct {
F []string `te:"range($, in(#v, '', 'ttp', 'euttp'))"`
}
@@ -96,5 +96,9 @@ func TestRangeIn(t *testing.T) {
F: a,
// F: b,
})
assert.Equal(t, []interface{}{true, true, true}, r.Eval("F"))
expect := []interface{}{true, true, true}
actual := r.Eval("F")
if !reflect.DeepEqual(expect, actual) {
t.Fatal("not equal", expect, actual)
}
}
12 changes: 4 additions & 8 deletions internal/tagexpr/spec_operand.go
Original file line number Diff line number Diff line change
@@ -21,8 +21,6 @@ import (
"regexp"
"strconv"
"strings"

"github.com/andeya/ameda"
)

// --------------------------- Operand ---------------------------
@@ -214,9 +212,7 @@ func readVariableExprNode(expr *string) ExprNode {
}
}


func getBoolAndSignOpposite(expr *string) (last string, boolOpposite *bool, signOpposite *bool) {
last = strings.TrimLeft(last, "+")
last, boolOpposite = getOpposite(expr, "!")
last = strings.TrimLeft(last, "+")
last, signOpposite = getOpposite(&last, "-")
@@ -241,7 +237,7 @@ func toString(i interface{}, enforce bool) (string, bool) {
case nil:
return "", false
default:
rv := ameda.DereferenceValue(reflect.ValueOf(i))
rv := dereferenceValue(reflect.ValueOf(i))
if rv.Kind() == reflect.String {
return rv.String(), true
}
@@ -258,7 +254,7 @@ func toString(i interface{}, enforce bool) (string, bool) {

func toFloat64(i interface{}, tryParse bool) (float64, bool) {
var v float64
var ok = true
ok := true
switch t := i.(type) {
case float64:
v = t
@@ -287,7 +283,7 @@ func toFloat64(i interface{}, tryParse bool) (float64, bool) {
case nil:
ok = false
default:
rv := ameda.DereferenceValue(reflect.ValueOf(t))
rv := dereferenceValue(reflect.ValueOf(t))
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v = float64(rv.Int())
@@ -346,7 +342,7 @@ func realValue(v interface{}, boolOpposite *bool, signOpposite *bool) interface{
t[k] = realValue(v, boolOpposite, signOpposite)
}
default:
rv := ameda.DereferenceValue(reflect.ValueOf(v))
rv := dereferenceValue(reflect.ValueOf(v))
switch rv.Kind() {
case reflect.String:
v = rv.String()
14 changes: 14 additions & 0 deletions internal/tagexpr/spec_range.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr

import (
38 changes: 29 additions & 9 deletions internal/tagexpr/spec_range_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr_test

import (
"reflect"
"testing"

"github.com/bytedance/go-tagexpr/v2"
"github.com/stretchr/testify/assert"
"github.com/cloudwego/hertz/internal/tagexpr"
)

func TestIssue12(t *testing.T) {
var vm = tagexpr.New("te")
vm := tagexpr.New("te")
type I int
type S struct {
F []I `te:"range($, '>'+sprintf('%v:%v', #k, #v+2+len($)))"`
@@ -25,10 +39,16 @@ func TestIssue12(t *testing.T) {
MFs: []map[string][]I{{"m": a}},
MFs2: []map[string][]I{},
})
assert.Equal(t, []interface{}{">0:6", ">1:7"}, r.Eval("F"))
assert.Equal(t, []interface{}{[]interface{}{">0:6", ">1:7"}}, r.Eval("Fs"))
assert.Equal(t, []interface{}{">m0:6", ">m1:7"}, r.Eval("M"))
assert.Equal(t, []interface{}{[]interface{}{[]interface{}{">0:6", ">1:7"}}}, r.Eval("MFs"))
assert.Equal(t, []interface{}{}, r.Eval("MFs2"))
assert.Equal(t, true, r.EvalBool("MFs2"))
assertEqual(t, []interface{}{">0:6", ">1:7"}, r.Eval("F"))
assertEqual(t, []interface{}{[]interface{}{">0:6", ">1:7"}}, r.Eval("Fs"))
assertEqual(t, []interface{}{[]interface{}{[]interface{}{">0:6", ">1:7"}}}, r.Eval("MFs"))
assertEqual(t, []interface{}{}, r.Eval("MFs2"))
assertEqual(t, true, r.EvalBool("MFs2"))

// result may not stable for map
got := r.Eval("M")
if !reflect.DeepEqual([]interface{}{">m0:6", ">m1:7"}, got) &&
!reflect.DeepEqual([]interface{}{">m1:7", ">m0:6"}, got) {
t.Fatal(got)
}
}
16 changes: 4 additions & 12 deletions internal/tagexpr/spec_test.go
Original file line number Diff line number Diff line change
@@ -16,13 +16,12 @@ package tagexpr

import (
"context"
"fmt"
"reflect"
"testing"
)

func TestReadPairedSymbol(t *testing.T) {
var cases = []struct {
cases := []struct {
left, right rune
expr, val, lastExprNode string
}{
@@ -45,7 +44,7 @@ func TestReadPairedSymbol(t *testing.T) {
}

func TestReadBoolExprNode(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
val bool
lastExprNode string
@@ -69,7 +68,7 @@ func TestReadBoolExprNode(t *testing.T) {
}

func TestReadDigitalExprNode(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
val float64
lastExprNode string
@@ -98,7 +97,7 @@ func TestReadDigitalExprNode(t *testing.T) {
}

func TestFindSelector(t *testing.T) {
var cases = []struct {
cases := []struct {
expr string
field string
name string
@@ -161,10 +160,3 @@ func TestFindSelector(t *testing.T) {
}
}
}
func printBoolPtr(b *bool) string {
var v interface{} = b
if b != nil {
v = *b
}
return fmt.Sprint(v)
}
54 changes: 25 additions & 29 deletions internal/tagexpr/tagexpr.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Package tagexpr is an interesting go struct tag expression syntax for field validation, etc.
//
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,6 +11,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package tagexpr is an interesting go struct tag expression syntax for field validation, etc.
package tagexpr

import (
@@ -23,8 +23,6 @@
"strings"
"sync"
"unsafe"

"github.com/andeya/ameda"
)

// Internally unified data types
@@ -115,21 +113,20 @@
var v reflect.Value
switch t := structPtrOrReflectValue.(type) {
case reflect.Value:
v = ameda.DereferenceValue(t)
v = dereferenceValue(t)
default:
v = ameda.DereferenceValue(reflect.ValueOf(t))
v = dereferenceValue(reflect.ValueOf(t))
}
if err := checkStructMapAddr(v); err != nil {
return nil, err
}

u := ameda.ValueFrom2(&v)
ptr := unsafe.Pointer(u.Pointer())
ptr := rvPtr(v)
if ptr == nil {
return nil, unsupportNil
}

tid := u.RuntimeTypeID()
tid := rvType(v)
var err error
vm.rw.RLock()
s, ok := vm.structJar[tid]
@@ -175,35 +172,34 @@
}

func (vm *VM) subRunAll(omitNil bool, tePath string, value reflect.Value, fn func(*TagExpr, error) error) error {
rv := ameda.DereferenceInterfaceValue(value)
rv := dereferenceInterfaceValue(value)
if !rv.IsValid() {
return nil
}
rt := ameda.DereferenceType(rv.Type())
rv = ameda.DereferenceValue(rv)
rt := dereferenceType(rv.Type())
rv = dereferenceValue(rv)
switch rt.Kind() {
case reflect.Struct:
if len(tePath) == 0 {
if err := checkStructMapAddr(rv); err != nil {
return err
}
}
u := ameda.ValueFrom2(&rv)
ptr := unsafe.Pointer(u.Pointer())
ptr := rvPtr(rv)
if ptr == nil {
if omitNil {
return nil
}
return fn(nil, unsupportNil)
}
return fn(vm.subRun(tePath, rt, u.RuntimeTypeID(), ptr))
return fn(vm.subRun(tePath, rt, rvType(rv), ptr))

case reflect.Slice, reflect.Array:
count := rv.Len()
if count == 0 {
return nil
}
switch ameda.DereferenceType(rv.Type().Elem()).Kind() {
switch dereferenceType(rv.Type().Elem()).Kind() {
case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map:
for i := count - 1; i >= 0; i-- {
err := vm.subRunAll(omitNil, tePath+"["+strconv.Itoa(i)+"]", rv.Index(i), fn)
@@ -221,11 +217,11 @@
}
var canKey, canValue bool
rt := rv.Type()
switch ameda.DereferenceType(rt.Key()).Kind() {
switch dereferenceType(rt.Key()).Kind() {
case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map:
canKey = true
}
switch ameda.DereferenceType(rt.Elem()).Kind() {
switch dereferenceType(rt.Elem()).Kind() {
case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map:
canValue = true
}
@@ -278,15 +274,15 @@
if err != nil {
return nil, err
}
tid := ameda.RuntimeTypeID(structType)
tid := rtType(structType)
s, had := vm.structJar[tid]
if had {
return s, s.err
}
s = vm.newStructVM()
s.name = structType.String()
vm.structJar[tid] = s
var numField = structType.NumField()
numField := structType.NumField()
var structField reflect.StructField
var sub *structVM
for i := 0; i < numField; i++ {
@@ -420,7 +416,7 @@
}

func (s *structVM) newFieldVM(structField reflect.StructField) (*fieldVM, bool, error) {
var tag = structField.Tag.Get(s.vm.tagName)
tag := structField.Tag.Get(s.vm.tagName)
if tag == tagOmit {
return nil, false, nil
}
@@ -437,15 +433,15 @@
s.fields[f.fieldSelector] = f
s.fieldSelectorList = append(s.fieldSelectorList, f.fieldSelector)

var t = structField.Type
t := structField.Type
var ptrDeep int
for t.Kind() == reflect.Ptr {
t = t.Elem()
ptrDeep++
}
f.ptrDeep = ptrDeep

var offset = structField.Offset
offset := structField.Offset
f.getPtr = func(ptr unsafe.Pointer) unsafe.Pointer {
if ptr == nil {
return nil
@@ -807,13 +803,13 @@
case nil, error:
return false
case []interface{}:
var bol = true
bol := true
for _, v := range r {
bol = bol && FakeBool(v)
}
return bol
default:
vv := ameda.DereferenceValue(reflect.ValueOf(v))
vv := dereferenceValue(reflect.ValueOf(v))
if vv.IsValid() || vv.IsZero() {
return false
}
@@ -877,7 +873,7 @@
//
// format: fieldName, fieldName.exprName, fieldName1.fieldName2.exprName1
// result types: float64, string, bool, nil
func (t *TagExpr) EvalWithEnv(exprSelector string, env map[string]interface{})interface{} {
func (t *TagExpr) EvalWithEnv(exprSelector string, env map[string]interface{}) interface{} {
expr, ok := t.s.exprs[exprSelector]
if !ok {
// Compatible with single mode or the expression with the name @
@@ -940,7 +936,7 @@
keyPath := f.fieldSelector + "{k}"
for _, key := range v.MapKeys() {
if mapKeyStructVM != nil {
p := unsafe.Pointer(ameda.ValueFrom(derefValue(key)).Pointer())
p := rvPtr(derefValue(key))
if omitNil && p == nil {
continue
}
@@ -955,7 +951,7 @@
}
}
if mapOrSliceElemStructVM != nil {
p := unsafe.Pointer(ameda.ValueFrom(derefValue(v.MapIndex(key))).Pointer())
p := rvPtr(derefValue(v.MapIndex(key)))
if omitNil && p == nil {
continue
}
@@ -975,7 +971,7 @@
// slice or array
for i := v.Len() - 1; i >= 0; i-- {
if mapOrSliceElemStructVM != nil {
p := unsafe.Pointer(ameda.ValueFrom(derefValue(v.Index(i))).Pointer())
p := rvPtr(derefValue(v.Index(i)))
if omitNil && p == nil {
continue
}
@@ -1122,7 +1118,7 @@
return v.Convert(t)
}

var float64Type = reflect.TypeOf(float64(0))

Check failure on line 1121 in internal/tagexpr/tagexpr.go

GitHub Actions / lint

var `float64Type` is unused (unused)

func splitFieldSelector(selector string) (dir, base string) {
idx := strings.LastIndex(selector, ExprNameSeparator)
175 changes: 80 additions & 95 deletions internal/tagexpr/tagexpr_test.go

Large diffs are not rendered by default.

29 changes: 19 additions & 10 deletions internal/tagexpr/tagparser.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr

import (
@@ -6,11 +20,6 @@ import (
"unicode"
)

type namedTagExpr struct {
exprSelector string
expr *Expr
}

const (
tagOmit = "-"
tagOmitNil = "?"
@@ -99,7 +108,7 @@ func splitExpr(one string) (key, val string) {
}

func readOneExpr(tag *string) (string, error) {
var s = *(trimRightSpace(trimLeftSpace(tag)))
s := *(trimRightSpace(trimLeftSpace(tag)))
s = strings.TrimLeft(s, ";")
if s == "" {
return "", nil
@@ -108,7 +117,7 @@ func readOneExpr(tag *string) (string, error) {
s += ";"
}
a := strings.SplitAfter(strings.Replace(s, "\\'", "##", -1), ";")
var idx = -1
idx := -1
var patch int
for _, v := range a {
idx += len(v)
@@ -140,16 +149,16 @@ func readPairedSymbol(p *string, left, right rune) *string {
return nil
}
s = s[1:]
var last1 = left
last1 := left
var last2 rune
var leftLevel, rightLevel int
var escapeIndexes = make(map[int]bool)
escapeIndexes := make(map[int]bool)
var realEqual, escapeEqual bool
for i, r := range s {
if realEqual, escapeEqual = equalRune(right, r, last1, last2); realEqual {
if leftLevel == rightLevel {
*p = s[i+1:]
var sub = make([]rune, 0, i)
sub := make([]rune, 0, i)
for k, v := range s[:i] {
if !escapeIndexes[k] {
sub = append(sub, v)
22 changes: 18 additions & 4 deletions internal/tagexpr/tagparser_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tagexpr

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
)

func TestTagparser(t *testing.T) {
@@ -68,9 +80,11 @@ func TestTagparser(t *testing.T) {
for _, c := range cases {
r, e := parseTag(c.tag.Get("tagexpr"))
if e != nil == c.fail {
assert.Equal(t, c.expect, r, c.tag)
if !reflect.DeepEqual(c.expect, r) {
t.Fatal(c.expect, r, c.tag)
}
} else {
assert.Failf(t, string(c.tag), "kvs:%v, err:%v", r, e)
t.Fatalf("tag:%s kvs:%v, err:%v", c.tag, r, e)
}
if e != nil {
t.Logf("tag:%q, errMsg:%v", c.tag, e)
101 changes: 101 additions & 0 deletions internal/tagexpr/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2024 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package tagexpr

import (
"reflect"
"unsafe"
)

func init() {
testhack()
}

func dereferenceValue(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
v = v.Elem()
}
return v
}

func dereferenceType(t reflect.Type) reflect.Type {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}

func dereferenceInterfaceValue(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Interface {
v = v.Elem()
}
return v
}

type rvtype struct { // reflect.Value
abiType uintptr
ptr unsafe.Pointer // data pointer
}

func rvPtr(rv reflect.Value) unsafe.Pointer {
return (*rvtype)(unsafe.Pointer(&rv)).ptr
}

func rvType(rv reflect.Value) uintptr {
return (*rvtype)(unsafe.Pointer(&rv)).abiType
}

func rtType(rt reflect.Type) uintptr {
type iface struct {
tab uintptr
data uintptr
}
return (*iface)(unsafe.Pointer(&rt)).data
}

// quick test make sure the hack above works
func testhack() {
type T1 struct {
a int
}
type T2 struct {
a int
}
p0 := &T1{1}
p1 := &T1{2}
p2 := &T2{3}

if rvPtr(reflect.ValueOf(p0)) != unsafe.Pointer(p0) ||
rvPtr(reflect.ValueOf(p0).Elem()) != unsafe.Pointer(p0) ||
rvPtr(reflect.ValueOf(p0)) == rvPtr(reflect.ValueOf(p1)) {
panic("rvPtr() compatibility issue found")
}

if rvType(reflect.ValueOf(p0)) != rvType(reflect.ValueOf(p1)) ||
rvType(reflect.ValueOf(p0)) == rvType(reflect.ValueOf(p2)) ||
rvType(reflect.ValueOf(p0).Elem()) != rvType(reflect.ValueOf(p1).Elem()) ||
rvType(reflect.ValueOf(p0).Elem()) == rvType(reflect.ValueOf(p2).Elem()) {
panic("rvType() compatibility issue found")
}

if rtType(reflect.TypeOf(p0)) != rtType(reflect.TypeOf(p1)) ||
rtType(reflect.TypeOf(p0)) == rtType(reflect.TypeOf(p2)) ||
rtType(reflect.TypeOf(p0).Elem()) != rtType(reflect.TypeOf(p1).Elem()) ||
rtType(reflect.TypeOf(p0).Elem()) == rtType(reflect.TypeOf(p2).Elem()) {
panic("rtType() compatibility issue found")
}
}
25 changes: 21 additions & 4 deletions internal/tagexpr/validator/default.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validator

var defaultValidator = New("vd").SetErrorFactory(defaultErrorFactory)

// Default returns the default validator.
// NOTE:
// The tag name is 'vd'
//
// The tag name is 'vd'
func Default() *Validator {
return defaultValidator
}

// Validate uses the default validator to validate whether the fields of value is valid.
// NOTE:
// The tag name is 'vd'
// If checkAll=true, validate all the error.
//
// The tag name is 'vd'
// If checkAll=true, validate all the error.
func Validate(value interface{}, checkAll ...bool) error {
return defaultValidator.Validate(value, checkAll...)
}

// SetErrorFactory customizes the factory of validation error for the default validator.
// NOTE:
// The tag name is 'vd'
//
// The tag name is 'vd'
func SetErrorFactory(errFactory func(fieldSelector, msg string) error) {
defaultValidator.SetErrorFactory(errFactory)
}
16 changes: 15 additions & 1 deletion internal/tagexpr/validator/example_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validator_test

import (
"fmt"

vd "github.com/bytedance/go-tagexpr/v2/validator"
vd "github.com/cloudwego/hertz/internal/tagexpr/validator"
)

func Example() {
18 changes: 16 additions & 2 deletions internal/tagexpr/validator/func.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validator

import (
@@ -6,7 +20,7 @@ import (

"github.com/nyaruka/phonenumbers"

"github.com/bytedance/go-tagexpr/v2"
"github.com/cloudwego/hertz/internal/tagexpr"
)

// ErrInvalidWithoutMsg verification error without error message.
@@ -46,7 +60,7 @@ func RegFunc(funcName string, fn func(args ...interface{}) error, force ...bool)
}

func init() {
var pattern = "^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$"
pattern := "^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$"
emailRegexp := regexp.MustCompile(pattern)
MustRegFunc("email", func(args ...interface{}) error {
if len(args) != 1 {
12 changes: 7 additions & 5 deletions internal/tagexpr/validator/validator.go
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,7 +22,7 @@ import (
"strings"
_ "unsafe"

tagexpr "github.com/bytedance/go-tagexpr/v2"
"github.com/cloudwego/hertz/internal/tagexpr"
)

const (
@@ -55,13 +55,14 @@ func (v *Validator) VM() *tagexpr.VM {

// Validate validates whether the fields of value is valid.
// NOTE:
// If checkAll=true, validate all the error.
//
// If checkAll=true, validate all the error.
func (v *Validator) Validate(value interface{}, checkAll ...bool) error {
var all bool
if len(checkAll) > 0 {
all = checkAll[0]
}
var errs = make([]error, 0, 8)
errs := make([]error, 0, 8)
err := v.vm.RunAny(value, func(te *tagexpr.TagExpr, err error) error {
if err != nil {
errs = append(errs, err)
@@ -130,7 +131,8 @@ func (v *Validator) Validate(value interface{}, checkAll ...bool) error {

// SetErrorFactory customizes the factory of validation error.
// NOTE:
// If errFactory==nil, the default is used
//
// If errFactory==nil, the default is used
func (v *Validator) SetErrorFactory(errFactory func(failPath, msg string) error) *Validator {
if errFactory == nil {
errFactory = defaultErrorFactory
129 changes: 79 additions & 50 deletions internal/tagexpr/validator/validator_test.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,58 @@
// Copyright 2019 Bytedance Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package validator_test

import (
"encoding/json"
"errors"
"testing"

"github.com/stretchr/testify/assert"

vd "github.com/bytedance/go-tagexpr/v2/validator"
vd "github.com/cloudwego/hertz/internal/tagexpr/validator"
)

func assertEqualError(t *testing.T, err error, s string) {
t.Helper()
if err.Error() != s {
t.Fatal("not equal", err, s)
}
}

func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}

func TestNil(t *testing.T) {
type F struct {
f struct {

Check failure on line 41 in internal/tagexpr/validator/validator_test.go

GitHub Actions / lint

field `f` is unused (unused)
g int `vd:"$%3==1"`
}
}
assert.EqualError(t, vd.Validate((*F)(nil)), "unsupport data: nil")
assertEqualError(t, vd.Validate((*F)(nil)), "unsupport data: nil")
}

func TestAll(t *testing.T) {
type T struct {
a string `vd:"email($)"`
f struct {
g int `vd:"$%3==1"`
A string `vd:"email($)"`
F struct {
G int `vd:"$%3==1"`
}
}
assert.EqualError(t, vd.Validate(new(T), true), "email format is incorrect\tinvalid parameter: f.g")
assertEqualError(t, vd.Validate(new(T), true), "email format is incorrect\tinvalid parameter: F.G")
}

func TestIssue1(t *testing.T) {
@@ -54,7 +80,7 @@
type BatchCreateEmailTaskRequest struct {
InfoList []*EmailTaskInfo
}
var invalid = "invalid email"
invalid := "invalid email"
req := &BatchCreateEmailTaskRequest{
InfoList: []*EmailTaskInfo{
{
@@ -68,7 +94,7 @@
},
},
}
assert.EqualError(t, vd.Validate(req, false), "email format is incorrect")
assertEqualError(t, vd.Validate(req, false), "email format is incorrect")
}

func TestIssue2(t *testing.T) {
@@ -82,7 +108,7 @@
},
}
v := vd.New("vd")
assert.NoError(t, v.Validate(A))
assertNoError(t, v.Validate(A))
}

func TestIssue3(t *testing.T) {
@@ -101,7 +127,7 @@
},
}
v := vd.New("vd")
assert.NoError(t, v.Validate(a))
assertNoError(t, v.Validate(a))
}

func TestIssue4(t *testing.T) {
@@ -118,23 +144,23 @@
v := vd.New("vd")

a := &A{}
assert.NoError(t, v.Validate(a))
assertNoError(t, v.Validate(a))

a = &A{F1: new(C)}
assert.EqualError(t, v.Validate(a), "index is nil")
assertEqualError(t, v.Validate(a), "index is nil")

a = &A{F2: map[string]*C{"x": &C{Index: new(int32)}}}
assert.EqualError(t, v.Validate(a), "invalid parameter: F2{v for k=x}.Index2")
a = &A{F2: map[string]*C{"x": {Index: new(int32)}}}
assertEqualError(t, v.Validate(a), "invalid parameter: F2{v for k=x}.Index2")

a = &A{F3: []*C{{Index: new(int32)}}}
assert.EqualError(t, v.Validate(a), "invalid parameter: F3[0].Index2")
assertEqualError(t, v.Validate(a), "invalid parameter: F3[0].Index2")

type B struct {
F1 *C `vd:"$!=nil"`
F2 *C
}
b := &B{}
assert.EqualError(t, v.Validate(b), "invalid parameter: F1")
assertEqualError(t, v.Validate(b), "invalid parameter: F1")

type D struct {
F1 *C
@@ -146,12 +172,11 @@
}
b.F1 = new(C)
e := &E{D: []*D{nil}}
assert.NoError(t, v.Validate(e))
assertNoError(t, v.Validate(e))
}

func TestIssue5(t *testing.T) {
type SubSheet struct {
}
type SubSheet struct{}
type CopySheet struct {
Source *SubSheet `json:"source" vd:"$!=nil"`
Destination *SubSheet `json:"destination" vd:"$!=nil"`
@@ -165,11 +190,15 @@
b := `{"requests": [{}]}`
var data BatchUpdateSheetRequestArg
err := json.Unmarshal([]byte(b), &data)
assert.NoError(t, err)
assert.Equal(t, 1, len(data.Requests))
assert.Nil(t, data.Requests[0].CopySheet)
assertNoError(t, err)
if len(data.Requests) != 1 {
t.Fatal(len(data.Requests))
}
if data.Requests[0].CopySheet != nil {
t.Fatal(data.Requests[0].CopySheet)
}
v := vd.New("vd")
assert.NoError(t, v.Validate(&data))
assertNoError(t, v.Validate(&data))
}

func TestIn(t *testing.T) {
@@ -183,44 +212,44 @@
v := vd.New("vd")
data := &T{}
err := v.Validate(data)
assert.EqualError(t, err, "invalid parameter: A")
assertEqualError(t, err, "invalid parameter: A")
data.A = "b"
err = v.Validate(data)
assert.EqualError(t, err, "invalid parameter: B")
assertEqualError(t, err, "invalid parameter: B")
data.B = 2
err = v.Validate(data)
assert.NoError(t, err)
assertNoError(t, err)

type T2 struct {
C string `vd:"in($)"`
}
data2 := &T2{}
err = v.Validate(data2)
assert.EqualError(t, err, "invalid parameter: C")
assertEqualError(t, err, "invalid parameter: C")

type T3 struct {
C string `vd:"in($,1)"`
}
data3 := &T3{}
err = v.Validate(data3)
assert.EqualError(t, err, "invalid parameter: C")
assertEqualError(t, err, "invalid parameter: C")
}

type (
Issue23A struct {
b *Issue23B
v int64 `vd:"$==0"`
B *Issue23B
V int64 `vd:"$==0"`
}
Issue23B struct {
a *Issue23A
v int64 `vd:"$==0"`
A *Issue23A
V int64 `vd:"$==0"`
}
)

func TestIssue23(t *testing.T) {
var data = &Issue23B{a: &Issue23A{b: new(Issue23B)}}
data := &Issue23B{A: &Issue23A{B: new(Issue23B)}}
err := vd.Validate(data, true)
assert.NoError(t, err)
assertNoError(t, err)
}

func TestIssue24(t *testing.T) {
@@ -251,9 +280,9 @@
type SubmitDoctorImportRequest struct {
SubmitDoctorImport []*SubmitDoctorImportItem `form:"submit_doctor_import,required" json:"submit_doctor_import,required"`
}
var data = &SubmitDoctorImportRequest{SubmitDoctorImport: []*SubmitDoctorImportItem{{}}}
data := &SubmitDoctorImportRequest{SubmitDoctorImport: []*SubmitDoctorImportItem{{}}}
err := vd.Validate(data, true)
assert.EqualError(t, err, "invalid parameter: SubmitDoctorImport[0].Idcard\tinvalid parameter: SubmitDoctorImport[0].PracCertNo\temail format is incorrect\tthe phone number supplied is not a number")
assertEqualError(t, err, "invalid parameter: SubmitDoctorImport[0].Idcard\tinvalid parameter: SubmitDoctorImport[0].PracCertNo\temail format is incorrect\tthe phone number supplied is not a number")
}

func TestStructSliceMap(t *testing.T) {
@@ -276,7 +305,7 @@
C: map[string][]map[string]F{"z": {{"zz": *f}}},
}
err := vd.Validate(s, true)
assert.EqualError(t, err, "invalid parameter: A{v for k=x}.f.g\tinvalid parameter: B[0]{v for k=y}.f.g\tinvalid parameter: C{v for k=z}[0]{v for k=zz}.f.g")
assertEqualError(t, err, "invalid parameter: A{v for k=x}.f.g\tinvalid parameter: B[0]{v for k=y}.f.g\tinvalid parameter: C{v for k=z}[0]{v for k=zz}.f.g")
}

func TestIssue30(t *testing.T) {
@@ -287,27 +316,27 @@
vd.RegFunc("gt", func(args ...interface{}) error {
return errors.New("force error")
})
assert.EqualError(t, vd.Validate(&TStruct{TOk: "1"}), "invalid parameter: TOk")
// assert.NoError(t, vd.Validate(&TStruct{TOk: "1", TFail: "1"}))
assertEqualError(t, vd.Validate(&TStruct{TOk: "1"}), "invalid parameter: TOk")
// assertNoError(t, vd.Validate(&TStruct{TOk: "1", TFail: "1"}))
}

func TestIssue31(t *testing.T) {
type TStruct struct {
A []int32 `vd:"$ == nil || ($ != nil && range($, in(#v, 1, 2, 3))"`
}
assert.EqualError(t, vd.Validate(&TStruct{A: []int32{1}}), "syntax error: \"($ != nil && range($, in(#v, 1, 2, 3))\"")
assert.EqualError(t, vd.Validate(&TStruct{A: []int32{1}}), "syntax error: \"($ != nil && range($, in(#v, 1, 2, 3))\"")
assert.EqualError(t, vd.Validate(&TStruct{A: []int32{1}}), "syntax error: \"($ != nil && range($, in(#v, 1, 2, 3))\"")
assertEqualError(t, vd.Validate(&TStruct{A: []int32{1}}), "syntax error: \"($ != nil && range($, in(#v, 1, 2, 3))\"")
assertEqualError(t, vd.Validate(&TStruct{A: []int32{1}}), "syntax error: \"($ != nil && range($, in(#v, 1, 2, 3))\"")
assertEqualError(t, vd.Validate(&TStruct{A: []int32{1}}), "syntax error: \"($ != nil && range($, in(#v, 1, 2, 3))\"")
}

func TestRegexp(t *testing.T) {
type TStruct struct {
A string `vd:"regexp('(\\d+\\.){3}\\d+')"`
}
assert.NoError(t, vd.Validate(&TStruct{A: "0.0.0.0"}))
assert.EqualError(t, vd.Validate(&TStruct{A: "0...0"}), "invalid parameter: A")
assert.EqualError(t, vd.Validate(&TStruct{A: "abc1"}), "invalid parameter: A")
assert.EqualError(t, vd.Validate(&TStruct{A: "0?0?0?0"}), "invalid parameter: A")
assertNoError(t, vd.Validate(&TStruct{A: "0.0.0.0"}))
assertEqualError(t, vd.Validate(&TStruct{A: "0...0"}), "invalid parameter: A")
assertEqualError(t, vd.Validate(&TStruct{A: "abc1"}), "invalid parameter: A")
assertEqualError(t, vd.Validate(&TStruct{A: "0?0?0?0"}), "invalid parameter: A")
}

func TestRangeIn(t *testing.T) {
@@ -317,9 +346,9 @@
err := vd.Validate(S{
F: []string{"ttp", "", "euttp"},
})
assert.NoError(t, err)
assertNoError(t, err)
err = vd.Validate(S{
F: []string{"ttp", "?", "euttp"},
})
assert.EqualError(t, err, "invalid parameter: F")
assertEqualError(t, err, "invalid parameter: F")
}
2 changes: 1 addition & 1 deletion pkg/app/server/binding/config.go
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ import (
"reflect"
"time"

exprValidator "github.com/bytedance/go-tagexpr/v2/validator"
exprValidator "github.com/cloudwego/hertz/internal/tagexpr/validator"
inDecoder "github.com/cloudwego/hertz/pkg/app/server/binding/internal/decoder"
hJson "github.com/cloudwego/hertz/pkg/common/json"
"github.com/cloudwego/hertz/pkg/protocol"
2 changes: 1 addition & 1 deletion pkg/app/server/binding/default.go
Original file line number Diff line number Diff line change
@@ -70,8 +70,8 @@ import (
"strings"
"sync"

exprValidator "github.com/bytedance/go-tagexpr/v2/validator"
"github.com/cloudwego/hertz/internal/bytesconv"
exprValidator "github.com/cloudwego/hertz/internal/tagexpr/validator"
inDecoder "github.com/cloudwego/hertz/pkg/app/server/binding/internal/decoder"
hJson "github.com/cloudwego/hertz/pkg/common/json"
"github.com/cloudwego/hertz/pkg/common/utils"

0 comments on commit a607728

Please sign in to comment.