-
Notifications
You must be signed in to change notification settings - Fork 557
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
refactor(binding): use internal tagexpr
* remove unused code in github.com/bytedance/go-tagexpr * remove personal repos github.com/henrylee2cn/*
Showing
29 changed files
with
518 additions
and
586 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,203 +1,3 @@ | ||
# go-tagexpr [](http://goreportcard.com/report/bytedance/go-tagexpr) [](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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters