Skip to content

Commit 5e098d6

Browse files
committedJun 6, 2019
chore: Change the separator between multiple expressions to ';'
Change-Id: Ie37c9fa2b7852f394c86153bc931fa5ee7905ca2
1 parent c6d971f commit 5e098d6

11 files changed

+295
-163
lines changed
 

‎README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func Example() {
3636
type T struct {
3737
A int `tagexpr:"$<0||$>=100"`
3838
B string `tagexpr:"len($)>1 && regexp('^\\w*$')"`
39-
C bool `tagexpr:"{expr1:(f.g)$>0 && $}{expr2:'C must be true when T.f.g>0'}"`
40-
d []string `tagexpr:"{@:len($)>0 && $[0]=='D'} {msg:sprintf('invalid d: %v',$)}"`
39+
C bool `tagexpr:"expr1:(f.g)$>0 && $; expr2:'C must be true when T.f.g>0'"`
40+
d []string `tagexpr:"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)"`
4141
e map[string]int `tagexpr:"len($)==$['len']"`
4242
e2 map[string]*int `tagexpr:"len($)==$['len']"`
4343
f struct {
@@ -100,7 +100,7 @@ type T struct {
100100
// Single model
101101
Field1 T1 `tagName:"expression"`
102102
// Multiple model
103-
Field2 T2 `tagName:"{exprName:expression} [{exprName2:expression2}]..."`
103+
Field2 T2 `tagName:"exprName:expression; [exprName2:expression2;]..."`
104104
...
105105
}
106106
```

‎binding/README.md

+15-15
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ import (
2020

2121
func Example() {
2222
type InfoRequest struct {
23-
Name string `api:"{path:'name'}"`
24-
Year []int `api:"{query:'year'}"`
25-
Email *string `api:"{body:'email'}{@:email($)}"`
26-
Friendly bool `api:"{body:'friendly'}"`
27-
Pie float32 `api:"{body:'pie'}{required:true}"`
28-
Hobby []string `api:"{body:'hobby'}"`
29-
BodyNotFound *int `api:"{body:'xxx'}"`
30-
Authorization string `api:"{header:'Authorization'}{required:true}{@:$=='Basic 123456'}"`
31-
SessionID string `api:"{cookie:'sessionid'}{required:true}"`
23+
Name string `api:"path:'name'"`
24+
Year []int `api:"query:'year'"`
25+
Email *string `api:"body:'email'; @:email($)"`
26+
Friendly bool `api:"body:'friendly'"`
27+
Pie float32 `api:"body:'pie'; required:true"`
28+
Hobby []string `api:"body:'hobby'"`
29+
BodyNotFound *int `api:"body:'xxx'"`
30+
Authorization string `api:"header:'Authorization'; required:true; @:$=='Basic 123456'"`
31+
SessionID string `api:"cookie:'sessionid'; required:true"`
3232
AutoBody string
3333
AutoQuery string
3434
AutoNotFound *string
@@ -92,18 +92,18 @@ The parameter position in HTTP request:
9292

9393
|expression|description|
9494
|---------------|-----------|
95-
|`{path:'$name'}`|URL path parameter
96-
|`{query:'$name'}`|URL query parameter
97-
|`{body:'$name'}`|The field in body, support:<br>`application/json`,<br>`application/x-www-form-urlencoded`,<br>`multipart/form-data`
98-
|`{header:'$name'}`|Header parameter
99-
|`{cookie:'$name'}`|Cookie parameter
95+
|`path:'$name'`|URL path parameter
96+
|`query:'$name'`|URL query parameter
97+
|`body:'$name'`|The field in body, support:<br>`application/json`,<br>`application/x-www-form-urlencoded`,<br>`multipart/form-data`
98+
|`header:'$name'`|Header parameter
99+
|`cookie:'$name'`|Cookie parameter
100100

101101
**NOTE:**
102102

103103
- `'$name'` is variable placeholder
104104
- If `'$name'` is empty, use the name of field
105105
- If no position is tagged, use `body` first, followed by `query`
106-
- Expression `{required:true}` indicates that the parameter is required
106+
- Expression `required:true` indicates that the parameter is required
107107

108108

109109
## Level

‎binding/bind_test.go

+66-66
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ import (
1717
func TestRawBody(t *testing.T) {
1818
type Recv struct {
1919
rawBody **struct {
20-
A []byte `api:"{raw_body:nil}"`
21-
B *[]byte `api:"{raw_body:nil}"`
22-
C **[]byte `api:"{raw_body:nil}"`
23-
D string `api:"{raw_body:nil}"`
24-
E *string `api:"{raw_body:nil}"`
25-
F **string `api:"{raw_body:nil}{@:len($)<3}{msg:'too long'}"`
20+
A []byte `api:"raw_body:nil"`
21+
B *[]byte `api:"raw_body:nil"`
22+
C **[]byte `api:"raw_body:nil"`
23+
D string `api:"raw_body:nil"`
24+
E *string `api:"raw_body:nil"`
25+
F **string `api:"raw_body:nil; @:len($)<3; msg:'too long'"`
2626
}
27-
S string `api:"{raw_body:nil}"`
27+
S string `api:"raw_body:nil"`
2828
}
2929
bodyBytes := []byte("rawbody.............")
3030
req := newRequest("", nil, nil, bytes.NewReader(bodyBytes))
@@ -49,13 +49,13 @@ func TestRawBody(t *testing.T) {
4949
func TestQueryString(t *testing.T) {
5050
type Recv struct {
5151
X **struct {
52-
A []string `api:"{query:'a'}"`
53-
B string `api:"{query:'b'}"`
54-
C *[]string `api:"{query:'c'}{required:true}"`
55-
D *string `api:"{query:'d'}"`
52+
A []string `api:"query:'a'"`
53+
B string `api:"query:'b'"`
54+
C *[]string `api:"query:'c'; required:true"`
55+
D *string `api:"query:'d'"`
5656
}
57-
Y string `api:"{query:'y'}{required:true}"`
58-
Z *string `api:"{query:'z'}"`
57+
Y string `api:"query:'y'; required:true"`
58+
Z *string `api:"query:'z'"`
5959
}
6060
req := newRequest("http://localhost:8080/?a=a1&a=a2&b=b1&c=c1&c=c2&d=d1&d=d2&y=y1", nil, nil, nil)
6161
recv := new(Recv)
@@ -73,13 +73,13 @@ func TestQueryString(t *testing.T) {
7373
func TestQueryNum(t *testing.T) {
7474
type Recv struct {
7575
X **struct {
76-
A []int `api:"{query:'a'}"`
77-
B int32 `api:"{query:'b'}"`
78-
C *[]uint16 `api:"{query:'c'}{required:true}"`
79-
D *float32 `api:"{query:'d'}"`
76+
A []int `api:"query:'a'"`
77+
B int32 `api:"query:'b'"`
78+
C *[]uint16 `api:"query:'c'; required:true"`
79+
D *float32 `api:"query:'d'"`
8080
}
81-
Y bool `api:"{query:'y'}{required:true}"`
82-
Z *int64 `api:"{query:'z'}"`
81+
Y bool `api:"query:'y'; required:true"`
82+
Z *int64 `api:"query:'z'"`
8383
}
8484
req := newRequest("http://localhost:8080/?a=11&a=12&b=21&c=31&c=32&d=41&d=42&y=true", nil, nil, nil)
8585
recv := new(Recv)
@@ -97,13 +97,13 @@ func TestQueryNum(t *testing.T) {
9797
func TestHeaderString(t *testing.T) {
9898
type Recv struct {
9999
X **struct {
100-
A []string `api:"{header:'X-A'}"`
101-
B string `api:"{header:'X-B'}"`
102-
C *[]string `api:"{header:'X-C'}{required:true}"`
103-
D *string `api:"{header:'X-D'}"`
100+
A []string `api:"header:'X-A'"`
101+
B string `api:"header:'X-B'"`
102+
C *[]string `api:"header:'X-C'; required:true"`
103+
D *string `api:"header:'X-D'"`
104104
}
105-
Y string `api:"{header:'X-Y'}{required:true}"`
106-
Z *string `api:"{header:'X-Z'}"`
105+
Y string `api:"header:'X-Y'; required:true"`
106+
Z *string `api:"header:'X-Z'"`
107107
}
108108
header := make(http.Header)
109109
header.Add("X-A", "a1")
@@ -130,13 +130,13 @@ func TestHeaderString(t *testing.T) {
130130
func TestHeaderNum(t *testing.T) {
131131
type Recv struct {
132132
X **struct {
133-
A []int `api:"{header:'X-A'}"`
134-
B int32 `api:"{header:'X-B'}"`
135-
C *[]uint16 `api:"{header:'X-C'}{required:true}"`
136-
D *float32 `api:"{header:'X-D'}"`
133+
A []int `api:"header:'X-A'"`
134+
B int32 `api:"header:'X-B'"`
135+
C *[]uint16 `api:"header:'X-C'; required:true"`
136+
D *float32 `api:"header:'X-D'"`
137137
}
138-
Y bool `api:"{header:'X-Y'}{required:true}"`
139-
Z *int64 `api:"{header:'X-Z'}"`
138+
Y bool `api:"header:'X-Y'; required:true"`
139+
Z *int64 `api:"header:'X-Z'"`
140140
}
141141
header := make(http.Header)
142142
header.Add("X-A", "11")
@@ -163,13 +163,13 @@ func TestHeaderNum(t *testing.T) {
163163
func TestCookieString(t *testing.T) {
164164
type Recv struct {
165165
X **struct {
166-
A []string `api:"{cookie:'a'}"`
167-
B string `api:"{cookie:'b'}"`
168-
C *[]string `api:"{cookie:'c'}{required:true}"`
169-
D *string `api:"{cookie:'d'}"`
166+
A []string `api:"cookie:'a'"`
167+
B string `api:"cookie:'b'"`
168+
C *[]string `api:"cookie:'c'; required:true"`
169+
D *string `api:"cookie:'d'"`
170170
}
171-
Y string `api:"{cookie:'y'}{required:true}"`
172-
Z *string `api:"{cookie:'z'}"`
171+
Y string `api:"cookie:'y'; required:true"`
172+
Z *string `api:"cookie:'z'"`
173173
}
174174
cookies := []*http.Cookie{
175175
{Name: "a", Value: "a1"},
@@ -197,13 +197,13 @@ func TestCookieString(t *testing.T) {
197197
func TestCookieNum(t *testing.T) {
198198
type Recv struct {
199199
X **struct {
200-
A []int `api:"{cookie:'a'}"`
201-
B int32 `api:"{cookie:'b'}"`
202-
C *[]uint16 `api:"{cookie:'c'}{required:true}"`
203-
D *float32 `api:"{cookie:'d'}"`
200+
A []int `api:"cookie:'a'"`
201+
B int32 `api:"cookie:'b'"`
202+
C *[]uint16 `api:"cookie:'c'; required:true"`
203+
D *float32 `api:"cookie:'d'"`
204204
}
205-
Y bool `api:"{cookie:'y'}{required:true}"`
206-
Z *int64 `api:"{cookie:'z'}"`
205+
Y bool `api:"cookie:'y'; required:true"`
206+
Z *int64 `api:"cookie:'z'"`
207207
}
208208
cookies := []*http.Cookie{
209209
{Name: "a", Value: "11"},
@@ -231,13 +231,13 @@ func TestCookieNum(t *testing.T) {
231231
func TestFormString(t *testing.T) {
232232
type Recv struct {
233233
X **struct {
234-
A []string `api:"{body:'a'}"`
235-
B string `api:"{body:'b'}"`
236-
C *[]string `api:"{body:'c'}{required:true}"`
237-
D *string `api:"{body:'d'}"`
234+
A []string `api:"body:'a'"`
235+
B string `api:"body:'b'"`
236+
C *[]string `api:"body:'c'; required:true"`
237+
D *string `api:"body:'d'"`
238238
}
239-
Y string `api:"{body:'y'}{required:true}"`
240-
Z *string `api:"{body:'z'}"`
239+
Y string `api:"body:'y'; required:true"`
240+
Z *string `api:"body:'z'"`
241241
}
242242
values := make(url.Values)
243243
values.Add("a", "a1")
@@ -273,13 +273,13 @@ func TestFormString(t *testing.T) {
273273
func TestFormNum(t *testing.T) {
274274
type Recv struct {
275275
X **struct {
276-
A []int `api:"{body:'a'}"`
277-
B int32 `api:"{body:'b'}"`
278-
C *[]uint16 `api:"{body:'c'}{required:true}"`
279-
D *float32 `api:"{body:'d'}"`
276+
A []int `api:"body:'a'"`
277+
B int32 `api:"body:'b'"`
278+
C *[]uint16 `api:"body:'c'; required:true"`
279+
D *float32 `api:"body:'d'"`
280280
}
281-
Y bool `api:"{body:'y'}{required:true}"`
282-
Z *int64 `api:"{body:'z'}"`
281+
Y bool `api:"body:'y'; required:true"`
282+
Z *int64 `api:"body:'z'"`
283283
}
284284
values := make(url.Values)
285285
values.Add("a", "11")
@@ -315,12 +315,12 @@ func TestFormNum(t *testing.T) {
315315
func TestJSON(t *testing.T) {
316316
type Recv struct {
317317
X **struct {
318-
A []string `api:"{body:'a'}"`
318+
A []string `api:"body:'a'"`
319319
B int32 `api:""`
320-
C *[]uint16 `api:"{required:true}"`
321-
D *float32 `api:"{body:'d'}"`
320+
C *[]uint16 `api:"required:true"`
321+
D *float32 `api:"body:'d'"`
322322
}
323-
Y string `api:"{body:'y'}{required:true}"`
323+
Y string `api:"body:'y'; required:true"`
324324
Z *int64 `api:""`
325325
}
326326

@@ -373,12 +373,12 @@ func (testPathParams) Get(name string) (string, bool) {
373373
func TestPath(t *testing.T) {
374374
type Recv struct {
375375
X **struct {
376-
A []string `api:"{path:'a'}"`
377-
B int32 `api:"{path:'b'}"`
378-
C *[]uint16 `api:"{path:'c'}{required:true}"`
379-
D *float32 `api:"{path:'d'}"`
376+
A []string `api:"path:'a'"`
377+
B int32 `api:"path:'b'"`
378+
C *[]uint16 `api:"path:'c'; required:true"`
379+
D *float32 `api:"path:'d'"`
380380
}
381-
Y string `api:"{path:'y'}{required:true}"`
381+
Y string `api:"path:'y'; required:true"`
382382
Z *int64
383383
}
384384

@@ -400,10 +400,10 @@ func TestAuto(t *testing.T) {
400400
X **struct {
401401
A []string `api:""`
402402
B int32 `api:""`
403-
C *[]uint16 `api:"{required:true}"`
403+
C *[]uint16 `api:"required:true"`
404404
D *float32
405405
}
406-
Y string `api:"{required:true}"`
406+
Y string `api:"required:true"`
407407
Z *int64
408408
}
409409
query := make(url.Values)

‎binding/example_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import (
1313

1414
func Example() {
1515
type InfoRequest struct {
16-
Name string `api:"{path:'name'}"`
17-
Year []int `api:"{query:'year'}"`
18-
Email *string `api:"{body:'email'}{@:email($)}"`
19-
Friendly bool `api:"{body:'friendly'}"`
20-
Pie float32 `api:"{body:'pie'}{required:true}"`
21-
Hobby []string `api:"{body:'hobby'}"`
22-
BodyNotFound *int `api:"{body:'xxx'}"`
23-
Authorization string `api:"{header:'Authorization'}{required:true}{@:$=='Basic 123456'}"`
24-
SessionID string `api:"{cookie:'sessionid'}{required:true}"`
16+
Name string `api:"path:'name'"`
17+
Year []int `api:"query:'year'"`
18+
Email *string `api:"body:'email'; @:email($)"`
19+
Friendly bool `api:"body:'friendly'"`
20+
Pie float32 `api:"body:'pie'; required:true"`
21+
Hobby []string `api:"body:'hobby'"`
22+
BodyNotFound *int `api:"body:'xxx'"`
23+
Authorization string `api:"header:'Authorization'; required:true; @:$=='Basic 123456'"`
24+
SessionID string `api:"cookie:'sessionid'; required:true"`
2525
AutoBody string
2626
AutoQuery string
2727
AutoNotFound *string

‎example_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ func Example() {
2424
type T struct {
2525
A int `tagexpr:"$<0||$>=100"`
2626
B string `tagexpr:"len($)>1 && regexp('^\\w*$')"`
27-
C bool `tagexpr:"{expr1:(f.g)$>0 && $}{expr2:'C must be true when T.f.g>0'}"`
28-
d []string `tagexpr:"{@:len($)>0 && $[0]=='D'} {msg:sprintf('invalid d: %v',$)}"`
27+
C bool `tagexpr:"expr1:(f.g)$>0 && $; expr2:'C must be true when T.f.g>0'"`
28+
d []string `tagexpr:"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)"`
2929
e map[string]int `tagexpr:"len($)==$['len']"`
3030
e2 map[string]*int `tagexpr:"len($)==$['len']"`
3131
f struct {

‎expr.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func parseExpr(expr string) (*Expr, error) {
3232
s := expr
3333
_, err := p.parseExprNode(&s, e)
3434
if err != nil {
35-
return nil, fmt.Errorf("%q (syntax incorrect): %s", expr, err.Error())
35+
return nil, fmt.Errorf("%q (syntax error): %s", expr, err.Error())
3636
}
3737
sortPriority(e.RightOperand())
3838
err = p.checkSyntax()

‎tagexpr_test.go

+18-18
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,19 @@ func Test(t *testing.T) {
9494
tagName: "tagexpr",
9595
structure: &struct {
9696
A int `tagexpr:"$>0&&$<10&&!''&&!!!0&&!nil&&$"`
97-
A2 int `tagexpr:"{@:$>0&&$<10}"`
98-
b string `tagexpr:"{is:$=='test'}{msg:sprintf('expect: test, but got: %s',$)}"`
97+
A2 int `tagexpr:"@:$>0&&$<10"`
98+
b string `tagexpr:"is:$=='test';msg:sprintf('expect: test, but got: %s',$)"`
9999
c float32 `tagexpr:"(A)$+$"`
100100
d *string `tagexpr:"$"`
101101
e **int `tagexpr:"$"`
102-
f *[3]int `tagexpr:"{x:len($)}"`
103-
g string `tagexpr:"{x:!regexp('xxx',$)}{y:regexp('g\\d{3}$')}"`
104-
h []string `tagexpr:"{x:$[1]}{y:$[10]}"`
105-
i map[string]int `tagexpr:"{x:$['a']}{y:$[0]} {z:$==nil}"`
106-
i2 *map[string]int `tagexpr:"{x:$['a']}{y:$[0]} {z:$}"`
107-
j, j2 iface `tagexpr:"{@:$==1} {y:$}"`
102+
f *[3]int `tagexpr:"x:len($)"`
103+
g string `tagexpr:"x:!regexp('xxx',$);y:regexp('g\\d{3}$')"`
104+
h []string `tagexpr:"x:$[1];y:$[10]"`
105+
i map[string]int `tagexpr:"x:$['a'];y:$[0];z:$==nil"`
106+
i2 *map[string]int `tagexpr:"x:$['a'];y:$[0];z:$"`
107+
j, j2 iface `tagexpr:"@:$==1;y:$"`
108108
k *iface `tagexpr:"$==nil"`
109-
m *struct{ i int } `tagexpr:"{@:$}{x:$['a']['x']}"`
109+
m *struct{ i int } `tagexpr:"@:$;x:$['a']['x']"`
110110
}{
111111
A: 5.0,
112112
A2: 5.0,
@@ -153,7 +153,7 @@ func Test(t *testing.T) {
153153
tagName: "tagexpr",
154154
structure: &struct {
155155
A int `tagexpr:"$>0&&$<10"`
156-
b string `tagexpr:"{is:$=='test'}{msg:sprintf('expect: test, but got: %s',$)}"`
156+
b string `tagexpr:"is:$=='test';msg:sprintf('expect: test, but got: %s',$)"`
157157
c struct {
158158
_ int
159159
d bool `tagexpr:"$"`
@@ -257,7 +257,7 @@ func TestFieldNotInit(t *testing.T) {
257257
b string
258258
c struct {
259259
_ int
260-
d *bool `expr:"{test:nil}"`
260+
d *bool `expr:"test:nil"`
261261
}
262262
e *struct {
263263
_ int
@@ -454,17 +454,17 @@ func TestOperator(t *testing.T) {
454454
C int `tagexpr:"-$+(M)$*(N)$/$%(D.B)$[2]+$==1"`
455455
D *Tmp1 `tagexpr:"(D.A)$!=nil"`
456456
E string `tagexpr:"((D.A)$=='1'&&len($)>1)||((D.A)$=='2'&&len($)>2)||((D.A)$=='3'&&len($)>3)"`
457-
F map[string]int `tagexpr:"{x:len($)}{y:$['a']>10&&$['b']>1}"`
458-
G *map[string]int `tagexpr:"{x:$['a']+(F)$['a']>20}"`
457+
F map[string]int `tagexpr:"x:len($);y:$['a']>10&&$['b']>1"`
458+
G *map[string]int `tagexpr:"x:$['a']+(F)$['a']>20"`
459459
H []string `tagexpr:"len($)>=1&&len($)<10&&$[0]=='123'&&$[1]!='456'"`
460460
I interface{} `tagexpr:"$!=nil"`
461461
K *string `tagexpr:"len((D.A)$)+len($)<10&&len((D.A)$+$)<10"`
462462
L **string `tagexpr:"false"`
463463
M float64 `tagexpr:"$/2>10&&$%2==0"`
464464
N *float64 `tagexpr:"($+$*$-$/$+1)/$==$+1"`
465465
O *[3]float64 `tagexpr:"$[0]>10&&$[0]<20||$[0]>20&&$[0]<30"`
466-
P *Tmp2 `tagexpr:"{x:$!=nil}{y:len((P.A.A)$)<=1&&(P.A.B)$[0]==1}{z:$['A']['C']==nil}{w:$['A']['B'][0]==1}{r:$[0][1][2]==3}{s1:$[2]==nil}{s2:$[0][3]==nil}{s3:(ZZ)$}{s4:(P.B)$!=nil}"`
467-
Q *Tmp2 `tagexpr:"{s1:$['A']['B']!=nil}{s2:(Q.A)$['B']!=nil}{s3:$['A']['C']==nil}{s4:(Q.A)$['C']==nil}{s5:(Q.A)$['B'][0]==1}{s6:$['X']['Z']==nil}"`
466+
P *Tmp2 `tagexpr:"x:$!=nil;y:len((P.A.A)$)<=1&&(P.A.B)$[0]==1;z:$['A']['C']==nil;w:$['A']['B'][0]==1;r:$[0][1][2]==3;s1:$[2]==nil;s2:$[0][3]==nil;s3:(ZZ)$;s4:(P.B)$!=nil"`
467+
Q *Tmp2 `tagexpr:"s1:$['A']['B']!=nil;s2:(Q.A)$['B']!=nil;s3:$['A']['C']==nil;s4:(Q.A)$['C']==nil;s5:(Q.A)$['B'][0]==1;s6:$['X']['Z']==nil"`
468468
}
469469

470470
k := "123456"
@@ -565,9 +565,9 @@ func TestStruct(t *testing.T) {
565565
D struct {
566566
X string `vd:"$"`
567567
}
568-
} `vd:"{@:$['D']['X']}"`
569-
C2 string `vd:"{@:(C)$['D']['X']}"`
570-
C3 string `vd:"{@:(C.D.X)$}"`
568+
} `vd:"@:$['D']['X']"`
569+
C2 string `vd:"@:(C)$['D']['X']"`
570+
C3 string `vd:"@:(C.D.X)$"`
571571
}
572572
}
573573
a := new(A)

‎tagparser.go

+97-44
Original file line numberDiff line numberDiff line change
@@ -6,69 +6,122 @@ import (
66
"unicode"
77
)
88

9+
type namedTagExpr struct {
10+
exprSelector string
11+
expr *Expr
12+
}
13+
914
func (f *fieldVM) parseExprs(tag string) error {
10-
raw := tag
11-
tag = strings.TrimSpace(tag)
12-
if tag == "" {
13-
return nil
15+
kvs, err := parseTag(tag)
16+
if err != nil {
17+
return err
1418
}
15-
if tag[0] != '{' {
16-
expr, err := parseExpr(tag)
19+
exprSelectorPrefix := f.structField.Name
20+
21+
for exprSelector, exprString := range kvs {
22+
expr, err := parseExpr(exprString)
1723
if err != nil {
1824
return err
1925
}
20-
exprSelector := f.structField.Name
26+
if exprSelector == ExprNameSeparator {
27+
exprSelector = exprSelectorPrefix
28+
} else {
29+
exprSelector = exprSelectorPrefix + ExprNameSeparator + exprSelector
30+
}
2131
f.exprs[exprSelector] = expr
2232
f.origin.exprs[exprSelector] = expr
2333
f.origin.exprSelectorList = append(f.origin.exprSelectorList, exprSelector)
24-
return nil
2534
}
26-
var subtag *string
27-
var idx int
28-
var exprSelector, exprStr string
35+
return nil
36+
}
37+
38+
func parseTag(tag string) (map[string]string, error) {
39+
s := tag
40+
ptr := &s
41+
kvs := make(map[string]string)
2942
for {
30-
subtag = readPairedSymbol(&tag, '{', '}')
31-
if subtag != nil {
32-
idx = strings.Index(*subtag, ":")
33-
if idx > 0 {
34-
exprSelector = strings.TrimSpace((*subtag)[:idx])
35-
switch exprSelector {
36-
case "":
37-
continue
38-
case ExprNameSeparator:
39-
exprSelector = f.structField.Name
40-
default:
41-
exprSelector = f.structField.Name + ExprNameSeparator + exprSelector
42-
}
43-
if _, had := f.origin.exprs[exprSelector]; had {
44-
return fmt.Errorf("duplicate expression name: %s", exprSelector)
45-
}
46-
exprStr = strings.TrimSpace((*subtag)[idx+1:])
47-
if exprStr != "" {
48-
if expr, err := parseExpr(exprStr); err == nil {
49-
f.exprs[exprSelector] = expr
50-
f.origin.exprs[exprSelector] = expr
51-
f.origin.exprSelectorList = append(f.origin.exprSelectorList, exprSelector)
52-
} else {
53-
return err
54-
}
55-
trimLeftSpace(&tag)
56-
if tag == "" {
57-
return nil
58-
}
59-
continue
60-
}
61-
}
43+
one, err := readOneExpr(ptr)
44+
if err != nil {
45+
return nil, err
46+
}
47+
if one == "" {
48+
return kvs, nil
49+
}
50+
key, val := splitExpr(one)
51+
if val == "" {
52+
return nil, fmt.Errorf("%q (syntax error): expression string can not be empty", tag)
6253
}
63-
return fmt.Errorf("syntax incorrect: %q", raw)
54+
if _, ok := kvs[key]; ok {
55+
return nil, fmt.Errorf("%q (syntax error): duplicate expression name %q", tag, key)
56+
}
57+
kvs[key] = val
58+
}
59+
}
60+
61+
func splitExpr(one string) (key, val string) {
62+
one = strings.TrimSpace(one)
63+
if one == "" {
64+
return DefaultExprName, ""
65+
}
66+
var rs []rune
67+
for _, r := range one {
68+
if r == '@' ||
69+
r == '_' ||
70+
(r >= '0' && r <= '9') ||
71+
(r >= 'A' && r <= 'Z') ||
72+
(r >= 'a' && r <= 'z') {
73+
rs = append(rs, r)
74+
} else {
75+
break
76+
}
77+
}
78+
key = string(rs)
79+
val = strings.TrimSpace(one[len(key):])
80+
if val == "" || val[0] != ':' {
81+
return DefaultExprName, one
82+
}
83+
val = val[1:]
84+
if key == "" {
85+
key = DefaultExprName
86+
}
87+
return key, val
88+
}
89+
90+
func readOneExpr(tag *string) (string, error) {
91+
var s = *(trimRightSpace(trimLeftSpace(tag)))
92+
s = strings.TrimLeft(s, ";")
93+
if s == "" {
94+
return "", nil
95+
}
96+
if s[len(s)-1] != ';' {
97+
s += ";"
6498
}
99+
a := strings.SplitAfter(strings.Replace(s, "\\'", "##", -1), ";")
100+
var idx = -1
101+
var patch int
102+
for _, v := range a {
103+
idx += len(v)
104+
if (strings.Count(v, "'")+patch)%2 == 0 {
105+
*tag = s[idx+1:]
106+
return s[:idx], nil
107+
}
108+
if strings.Contains(v, "'") {
109+
patch++
110+
}
111+
}
112+
return "", fmt.Errorf("%q (syntax error): unclosed single quote \"'\"", s)
65113
}
66114

67115
func trimLeftSpace(p *string) *string {
68116
*p = strings.TrimLeftFunc(*p, unicode.IsSpace)
69117
return p
70118
}
71119

120+
func trimRightSpace(p *string) *string {
121+
*p = strings.TrimRightFunc(*p, unicode.IsSpace)
122+
return p
123+
}
124+
72125
func readPairedSymbol(p *string, left, right rune) *string {
73126
s := *p
74127
if len(s) == 0 || rune(s[0]) != left {

‎tagparser_test.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package tagexpr
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestTagparser(t *testing.T) {
11+
cases := []struct {
12+
tag reflect.StructTag
13+
expect map[string]string
14+
fail bool
15+
}{
16+
{
17+
tag: `tagexpr:"$>0"`,
18+
expect: map[string]string{
19+
"@": "$>0",
20+
},
21+
}, {
22+
tag: `tagexpr:"$>0;'xxx'"`,
23+
fail: true,
24+
}, {
25+
tag: `tagexpr:"$>0;b:sprintf('%[1]T; %[1]v',(X)$)"`,
26+
expect: map[string]string{
27+
"@": `$>0`,
28+
"b": `sprintf('%[1]T; %[1]v',(X)$)`,
29+
},
30+
}, {
31+
tag: `tagexpr:"a:$=='0;1;';b:sprintf('%[1]T; %[1]v',(X)$)"`,
32+
expect: map[string]string{
33+
"a": `$=='0;1;'`,
34+
"b": `sprintf('%[1]T; %[1]v',(X)$)`,
35+
},
36+
}, {
37+
tag: `tagexpr:"a:1;;b:2"`,
38+
expect: map[string]string{
39+
"a": `1`,
40+
"b": `2`,
41+
},
42+
}, {
43+
tag: `tagexpr:";a:1;;b:2;;;"`,
44+
expect: map[string]string{
45+
"a": `1`,
46+
"b": `2`,
47+
},
48+
}, {
49+
tag: `tagexpr:";a:'123\\'';;b:'1\\'23';c:'1\\'2\\'3';;"`,
50+
expect: map[string]string{
51+
"a": `'123\''`,
52+
"b": `'1\'23'`,
53+
"c": `'1\'2\'3'`,
54+
},
55+
}, {
56+
tag: `tagexpr:"email($)"`,
57+
expect: map[string]string{
58+
"@": `email($)`,
59+
},
60+
}, {
61+
tag: `tagexpr:"false"`,
62+
expect: map[string]string{
63+
"@": `false`,
64+
},
65+
},
66+
}
67+
68+
for _, c := range cases {
69+
r, e := parseTag(c.tag.Get("tagexpr"))
70+
if e != nil == c.fail {
71+
assert.Equal(t, c.expect, r, c.tag)
72+
} else {
73+
assert.Failf(t, string(c.tag), "kvs:%v, err:%v", r, e)
74+
}
75+
if e != nil {
76+
t.Logf("tag:%q, errMsg:%v", c.tag, e)
77+
}
78+
}
79+
}

‎validator/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ func Example() {
5757
fmt.Println(vd.Validate(b) == nil)
5858

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

6666
type D struct {
67-
d []string `vd:"{@:len($)>0 && $[0]=='D'} {msg:sprintf('invalid d: %v',$)}"`
67+
d []string `vd:"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)"`
6868
}
6969
d := &D{d: []string{"x", "y"}}
7070
fmt.Println(vd.Validate(d))
@@ -122,7 +122,7 @@ type T struct {
122122
// Simple model
123123
Field1 T1 `tagName:"expression"`
124124
// Specify error message mode
125-
Field2 T2 `tagName:"{@:expression}{msg:expression2}"`
125+
Field2 T2 `tagName:"@:expression; msg:expression2"`
126126
...
127127
}
128128
```

‎validator/example_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ func Example() {
3838
fmt.Println(vd.Validate(b) == nil)
3939

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

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

0 commit comments

Comments
 (0)
Please sign in to comment.