Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
cache: false # don't use cache for self-hosted runners

- name: Unit Test
run: go test -covermode=atomic -coverprofile=coverage.txt ./...
run: go test -coverpkg=./... -coverprofile=coverage.txt ./...

- uses: codecov/codecov-action@v5
with:
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ require (
github.com/bytedance/sonic v1.14.0
github.com/cloudwego/netpoll v0.7.0
github.com/fsnotify/fsnotify v1.5.4
github.com/nyaruka/phonenumbers v1.0.55
github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.14.4
golang.org/x/sync v0.8.0
Expand All @@ -20,7 +19,6 @@ require (
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/gopkg v0.1.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
Expand Down
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
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/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
Expand All @@ -31,8 +27,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -106,8 +100,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 1 addition & 1 deletion internal/tagexpr/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# go-tagexpr

originally from https://github.com/bytedance/go-tagexpr
originally from https://github.com/bytedance/go-tagexpr v2.9.2
9 changes: 4 additions & 5 deletions internal/tagexpr/tagexpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,11 +820,10 @@ func FakeBool(v interface{}) bool {
}
return bol
default:
vv := dereferenceValue(reflect.ValueOf(v))
if vv.IsValid() || vv.IsZero() {
return false
}
return true
// https://github.com/bytedance/go-tagexpr/blob/v2.9.2/tagexpr.go#L801
// the original implementation either returns false or panics for default case
// we always return false for unsupported types to avoid introducing new behavior
return false
}
}

Expand Down
63 changes: 63 additions & 0 deletions internal/tagexpr/tagexpr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package tagexpr_test

import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
Expand Down Expand Up @@ -869,3 +870,65 @@ func TestHertzIssue1410(t *testing.T) {
t.Fatal(err)
}
}

func TestFakeBool(t *testing.T) {
// Numeric types - zero values should be false, non-zero should be true
tests := []struct {
input interface{}
expected bool
}{
// Float types
{float64(0), false},
{float64(3.14), true},
{float32(0), false},
{float32(2.5), true},

// Integer types
{int(0), false}, {int(42), true},
{int8(0), false}, {int8(127), true},
{int16(0), false}, {int16(32767), true},
{int32(0), false}, {int32(2147483647), true},
{int64(0), false}, {int64(9223372036854775807), true},

// Unsigned integer types
{uint(0), false}, {uint(1), true},
{uint8(0), false}, {uint8(255), true},
{uint16(0), false}, {uint16(65535), true},
{uint32(0), false}, {uint32(4294967295), true},
{uint64(0), false}, {uint64(18446744073709551615), true},

// String type
{"", false},
{"hello", true},

// Boolean type
{true, true},
{false, false},

// Nil and error types
{nil, false},
{errors.New("test"), false},

// Slice of interfaces - all elements must be truthy for true
{[]interface{}{}, true}, // empty slice -> true
{[]interface{}{1, "hello", true}, true}, // all truthy -> true
{[]interface{}{1, "", true}, false}, // one falsy -> false
{[]interface{}{0, "", false}, false}, // all falsy -> false
{[]interface{}{nil, nil}, false}, // nil values are falsy -> false

// Unsupported types should return false
{struct{}{}, false},
{new(int), false},
{make(chan int), false},
{func() {}, false},
{map[string]int{}, false},
{[3]int{1, 2, 3}, false},
}

for _, tt := range tests {
result := tagexpr.FakeBool(tt.input)
if result != tt.expected {
t.Errorf("FakeBool(%v) = %v; want %v", tt.input, result, tt.expected)
}
}
}
205 changes: 2 additions & 203 deletions internal/tagexpr/validator/README.md
Original file line number Diff line number Diff line change
@@ -1,204 +1,3 @@
# validator [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/bytedance/go-tagexpr/v2/validator)
# validator

A powerful validator 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 registers validator function expression
- Built-in len, sprintf, regexp, email, phone functions
- Support simple mode, or specify error message mode
- Use offset pointers to directly take values, better performance
- Required go version ≥1.9

## Example

```go
package validator_test

import (
"fmt"

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

func Example() {
type InfoRequest struct {
Name string `vd:"($!='Alice'||(Age)$==18) && regexp('\\w')"`
Age int `vd:"$>0"`
Email string `vd:"email($)"`
Phone1 string `vd:"phone($)"`
OtherPhones []string `vd:"range($, phone(#v,'CN'))"`
*InfoRequest `vd:"?"`
Info1 *InfoRequest `vd:"?"`
Info2 *InfoRequest `vd:"-"`
}
info := &InfoRequest{
Name: "Alice",
Age: 18,
Email: "henrylee2cn@gmail.com",
Phone1: "+8618812345678",
OtherPhones: []string{"18812345679", "18812345680"},
}
fmt.Println(vd.Validate(info))

type A struct {
A int `vd:"$<0||$>=100"`
Info interface{}
}
info.Email = "xxx"
a := &A{A: 107, Info: info}
fmt.Println(vd.Validate(a))
type B struct {
B string `vd:"len($)>1 && regexp('^\\w*$')"`
}
b := &B{"abc"}
fmt.Println(vd.Validate(b) == nil)

type C struct {
C bool `vd:"@:(S.A)$>0 && !$; msg:'C must be false when S.A>0'"`
S *A
}
c := &C{C: true, S: a}
fmt.Println(vd.Validate(c))

type D struct {
d []string `vd:"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)"`
}
d := &D{d: []string{"x", "y"}}
fmt.Println(vd.Validate(d))

type E struct {
e map[string]int `vd:"len($)==$['len']"`
}
e := &E{map[string]int{"len": 2}}
fmt.Println(vd.Validate(e))

// Customizes the factory of validation error.
vd.SetErrorFactory(func(failPath, msg string) error {
return fmt.Errorf(`{"succ":false, "error":"validation failed: %s"}`, failPath)
})

type F struct {
f struct {
g int `vd:"$%3==0"`
}
}
f := &F{}
f.f.g = 10
fmt.Println(vd.Validate(f))

fmt.Println(vd.Validate(map[string]*F{"a": f}))
fmt.Println(vd.Validate(map[string]map[string]*F{"a": {"b": f}}))
fmt.Println(vd.Validate([]map[string]*F{{"a": f}}))
fmt.Println(vd.Validate(struct {
A []map[string]*F
}{A: []map[string]*F{{"x": f}}}))
fmt.Println(vd.Validate(map[*F]int{f: 1}))
fmt.Println(vd.Validate([][1]*F{{f}}))
fmt.Println(vd.Validate((*F)(nil)))
fmt.Println(vd.Validate(map[string]*F{}))
fmt.Println(vd.Validate(map[string]map[string]*F{}))
fmt.Println(vd.Validate([]map[string]*F{}))
fmt.Println(vd.Validate([]*F{}))

// Output:
// <nil>
// email format is incorrect
// true
// C must be false when S.A>0
// invalid d: [x y]
// invalid parameter: e
// {"succ":false, "error":"validation failed: f.g"}
// {"succ":false, "error":"validation failed: {v for k=a}.f.g"}
// {"succ":false, "error":"validation failed: {v for k=a}{v for k=b}.f.g"}
// {"succ":false, "error":"validation failed: [0]{v for k=a}.f.g"}
// {"succ":false, "error":"validation failed: A[0]{v for k=x}.f.g"}
// {"succ":false, "error":"validation failed: {k}.f.g"}
// {"succ":false, "error":"validation failed: [0][0].f.g"}
// unsupported data: nil
// <nil>
// <nil>
// <nil>
// <nil>
}
```

## Syntax

Struct tag syntax spec:

```
type T struct {
// Simple model
Field1 T1 `tagName:"expression"`
// Specify error message mode
Field2 T2 `tagName:"@:expression; msg:expression2"`
// Omit it
Field3 T3 `tagName:"-"`
// Omit it when it is nil
Field4 T4 `tagName:"?"`
...
}
```

|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|
|`email((X)$)`|Regular match the struct field X, return true if it is email|
|`phone((X)$,<'defaultRegion'>)`|Regular match the struct field X, return true if it is phone|

<!-- |`(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`
* `*` `/` `%`
* `+` `-`
* `<` `<=` `>` `>=`
* `==` `!=`
* `&&`
* `||`
originally from https://github.com/bytedance/go-tagexpr v2.9.2
Loading
Loading