github.com/bytedance/go-tagexpr/v2@v2.9.8/binding/bind_test.go (about) 1 package binding_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "io/ioutil" 8 "mime/multipart" 9 "net/http" 10 "net/url" 11 "reflect" 12 "strings" 13 "testing" 14 "time" 15 16 // "github.com/bytedance/go-tagexpr/v2/binding/gjson" 17 "github.com/andeya/ameda" 18 "github.com/andeya/goutil/httpbody" 19 vd "github.com/bytedance/go-tagexpr/v2/validator" 20 "github.com/davecgh/go-spew/spew" 21 "github.com/stretchr/testify/assert" 22 23 "github.com/bytedance/go-tagexpr/v2/binding" 24 ) 25 26 func init() { 27 // gjson.UseJSONUnmarshaler() 28 } 29 30 func TestRawBody(t *testing.T) { 31 type Recv struct { 32 S []byte `raw_body:""` 33 F **string `raw_body:"" vd:"@:len($)<3; msg:'f too long'"` 34 } 35 bodyBytes := []byte("raw_body.............") 36 req := newRequest("", nil, nil, bytes.NewReader(bodyBytes)) 37 recv := new(Recv) 38 binder := binding.New(nil) 39 err := binder.BindAndValidate(recv, req, nil) 40 assert.EqualError(t, err, "validating: expr_path=F, cause=f too long") 41 assert.Equal(t, bodyBytes, []byte(recv.S)) 42 bodyCopied, err := binding.GetBody(req) 43 assert.NoError(t, err) 44 assert.Equal(t, bodyBytes, bodyCopied.Bytes()) 45 t.Logf("%s", bodyCopied) 46 } 47 48 func TestQueryString(t *testing.T) { 49 type metric string 50 type count int32 51 52 type Recv struct { 53 X **struct { 54 A []string `query:"a"` 55 B string `query:"b"` 56 C *[]string `query:"c,required"` 57 D *string `query:"d"` 58 E *[]***int `query:"e"` 59 F metric `query:"f"` 60 G []count `query:"g"` 61 } 62 Y string `query:"y,required"` 63 Z *string `query:"z"` 64 } 65 req := newRequest("http://localhost:8080/?a=a1&a=a2&b=b1&c=c1&c=c2&d=d1&d=d&f=qps&g=1002&g=1003&e=&e=2&y=y1", nil, nil, nil) 66 recv := new(Recv) 67 binder := binding.New(nil) 68 err := binder.BindAndValidate(recv, req, nil) 69 assert.EqualError(t, err, "binding: expr_path=X.E, cause=parameter type does not match binding data") 70 binder.SetLooseZeroMode(true) 71 err = binder.BindAndValidate(recv, req, nil) 72 assert.NoError(t, err) 73 assert.Equal(t, 0, ***(*(**recv.X).E)[0]) 74 assert.Equal(t, 2, ***(*(**recv.X).E)[1]) 75 assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A) 76 assert.Equal(t, "b1", (**recv.X).B) 77 assert.Equal(t, []string{"c1", "c2"}, *(**recv.X).C) 78 assert.Equal(t, "d1", *(**recv.X).D) 79 assert.Equal(t, metric("qps"), (**recv.X).F) 80 assert.Equal(t, []count{1002, 1003}, (**recv.X).G) 81 assert.Equal(t, "y1", recv.Y) 82 assert.Equal(t, (*string)(nil), recv.Z) 83 } 84 85 func TestGetBody(t *testing.T) { 86 type Recv struct { 87 X **struct { 88 E string `json:"e,required" query:"e,required"` 89 } 90 } 91 req := newRequest("http://localhost:8080/", nil, nil, nil) 92 recv := new(Recv) 93 binder := binding.New(nil) 94 err := binder.BindAndValidate(recv, req, nil) 95 assert.EqualError(t, err, "binding: expr_path=X.e, cause=missing required parameter") 96 } 97 98 func TestQueryNum(t *testing.T) { 99 type Recv struct { 100 X **struct { 101 A []int `query:"a"` 102 B int32 `query:"b"` 103 C *[]uint16 `query:"c,required"` 104 D *float32 `query:"d"` 105 } 106 Y bool `query:"y,required"` 107 Z *int64 `query:"z"` 108 } 109 req := newRequest("http://localhost:8080/?a=11&a=12&b=21&c=31&c=32&d=41&d=42&y=true", nil, nil, nil) 110 recv := new(Recv) 111 binder := binding.New(nil) 112 err := binder.BindAndValidate(recv, req, nil) 113 assert.NoError(t, err) 114 assert.Equal(t, []int{11, 12}, (**recv.X).A) 115 assert.Equal(t, int32(21), (**recv.X).B) 116 assert.Equal(t, &[]uint16{31, 32}, (**recv.X).C) 117 assert.Equal(t, float32(41), *(**recv.X).D) 118 assert.Equal(t, true, recv.Y) 119 assert.Equal(t, (*int64)(nil), recv.Z) 120 } 121 122 func TestHeaderString(t *testing.T) { 123 type Recv struct { 124 X **struct { 125 A []string `header:"X-A"` 126 B string `header:"X-B"` 127 C *[]string `header:"X-C,required"` 128 D *string `header:"X-D"` 129 } 130 Y string `header:"X-Y,required"` 131 Z *string `header:"X-Z"` 132 } 133 header := make(http.Header) 134 header.Add("X-A", "a1") 135 header.Add("X-A", "a2") 136 header.Add("X-B", "b1") 137 header.Add("X-C", "c1") 138 header.Add("X-C", "c2") 139 header.Add("X-D", "d1") 140 header.Add("X-D", "d2") 141 header.Add("X-Y", "y1") 142 req := newRequest("", header, nil, nil) 143 recv := new(Recv) 144 binder := binding.New(nil) 145 err := binder.BindAndValidate(recv, req, nil) 146 assert.NoError(t, err) 147 assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A) 148 assert.Equal(t, "b1", (**recv.X).B) 149 assert.Equal(t, []string{"c1", "c2"}, *(**recv.X).C) 150 assert.Equal(t, "d1", *(**recv.X).D) 151 assert.Equal(t, "y1", recv.Y) 152 assert.Equal(t, (*string)(nil), recv.Z) 153 } 154 155 func TestHeaderNum(t *testing.T) { 156 type Recv struct { 157 X **struct { 158 A []int `header:"X-A"` 159 B int32 `header:"X-B"` 160 C *[]uint16 `header:"X-C,required"` 161 D *float32 `header:"X-D"` 162 } 163 Y bool `header:"X-Y,required"` 164 Z *int64 `header:"X-Z"` 165 } 166 header := make(http.Header) 167 header.Add("X-A", "11") 168 header.Add("X-A", "12") 169 header.Add("X-B", "21") 170 header.Add("X-C", "31") 171 header.Add("X-C", "32") 172 header.Add("X-D", "41") 173 header.Add("X-D", "42") 174 header.Add("X-Y", "true") 175 req := newRequest("", header, nil, nil) 176 recv := new(Recv) 177 binder := binding.New(nil) 178 err := binder.BindAndValidate(recv, req, nil) 179 assert.NoError(t, err) 180 assert.Equal(t, []int{11, 12}, (**recv.X).A) 181 assert.Equal(t, int32(21), (**recv.X).B) 182 assert.Equal(t, &[]uint16{31, 32}, (**recv.X).C) 183 assert.Equal(t, float32(41), *(**recv.X).D) 184 assert.Equal(t, true, recv.Y) 185 assert.Equal(t, (*int64)(nil), recv.Z) 186 } 187 188 func TestCookieString(t *testing.T) { 189 type Recv struct { 190 X **struct { 191 A []string `cookie:"a"` 192 B string `cookie:"b"` 193 C *[]string `cookie:"c,required"` 194 D *string `cookie:"d"` 195 } 196 Y string `cookie:"y,required"` 197 Z *string `cookie:"z"` 198 } 199 cookies := []*http.Cookie{ 200 {Name: "a", Value: "a1"}, 201 {Name: "a", Value: "a2"}, 202 {Name: "b", Value: "b1"}, 203 {Name: "c", Value: "c1"}, 204 {Name: "c", Value: "c2"}, 205 {Name: "d", Value: "d1"}, 206 {Name: "d", Value: "d2"}, 207 {Name: "y", Value: "y1"}, 208 } 209 req := newRequest("", nil, cookies, nil) 210 recv := new(Recv) 211 binder := binding.New(nil) 212 err := binder.BindAndValidate(recv, req, nil) 213 assert.NoError(t, err) 214 assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A) 215 assert.Equal(t, "b1", (**recv.X).B) 216 assert.Equal(t, []string{"c1", "c2"}, *(**recv.X).C) 217 assert.Equal(t, "d1", *(**recv.X).D) 218 assert.Equal(t, "y1", recv.Y) 219 assert.Equal(t, (*string)(nil), recv.Z) 220 } 221 222 func TestCookieNum(t *testing.T) { 223 type Recv struct { 224 X **struct { 225 A []int `cookie:"a"` 226 B int32 `cookie:"b"` 227 C *[]uint16 `cookie:"c,required"` 228 D *float32 `cookie:"d"` 229 } 230 Y bool `cookie:"y,required"` 231 Z *int64 `cookie:"z"` 232 } 233 cookies := []*http.Cookie{ 234 {Name: "a", Value: "11"}, 235 {Name: "a", Value: "12"}, 236 {Name: "b", Value: "21"}, 237 {Name: "c", Value: "31"}, 238 {Name: "c", Value: "32"}, 239 {Name: "d", Value: "41"}, 240 {Name: "d", Value: "42"}, 241 {Name: "y", Value: "t"}, 242 } 243 req := newRequest("", nil, cookies, nil) 244 recv := new(Recv) 245 binder := binding.New(nil) 246 err := binder.BindAndValidate(recv, req, nil) 247 assert.NoError(t, err) 248 assert.Equal(t, []int{11, 12}, (**recv.X).A) 249 assert.Equal(t, int32(21), (**recv.X).B) 250 assert.Equal(t, &[]uint16{31, 32}, (**recv.X).C) 251 assert.Equal(t, float32(41), *(**recv.X).D) 252 assert.Equal(t, true, recv.Y) 253 assert.Equal(t, (*int64)(nil), recv.Z) 254 } 255 256 func TestFormString(t *testing.T) { 257 type Recv struct { 258 X **struct { 259 A []string `form:"a"` 260 B string `form:"b"` 261 C *[]string `form:"c,required"` 262 D *string `form:"d"` 263 } 264 Y string `form:"y,required"` 265 Z *string `form:"z"` 266 F *multipart.FileHeader `form:"F1"` 267 F1 multipart.FileHeader 268 Fs []multipart.FileHeader `form:"F1"` 269 Fs1 []*multipart.FileHeader `form:"F1"` 270 } 271 values := make(url.Values) 272 values.Add("a", "a1") 273 values.Add("a", "a2") 274 values.Add("b", "b1") 275 values.Add("c", "c1") 276 values.Add("c", "c2") 277 values.Add("d", "d1") 278 values.Add("d", "d2") 279 values.Add("y", "y1") 280 for i, f := range []httpbody.Files{nil, { 281 "F1": []httpbody.File{ 282 httpbody.NewFile("txt", strings.NewReader("0123")), 283 }, 284 }} { 285 contentType, bodyReader := httpbody.NewFormBody2(values, f) 286 header := make(http.Header) 287 header.Set("Content-Type", contentType) 288 req := newRequest("", header, nil, bodyReader) 289 recv := new(Recv) 290 binder := binding.New(nil) 291 err := binder.BindAndValidate(recv, req, nil) 292 assert.NoError(t, err) 293 assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A) 294 assert.Equal(t, "b1", (**recv.X).B) 295 assert.Equal(t, []string{"c1", "c2"}, *(**recv.X).C) 296 assert.Equal(t, "d1", *(**recv.X).D) 297 assert.Equal(t, "y1", recv.Y) 298 assert.Equal(t, (*string)(nil), recv.Z) 299 t.Logf("[%d] F: %#v", i, recv.F) 300 t.Logf("[%d] F1: %#v", i, recv.F1) 301 t.Logf("[%d] Fs: %#v", i, recv.Fs) 302 t.Logf("[%d] Fs1: %#v", i, recv.Fs1) 303 if len(recv.Fs1) > 0 { 304 t.Logf("[%d] Fs1[0]: %#v", i, recv.Fs1[0]) 305 } 306 } 307 } 308 309 func TestFormNum(t *testing.T) { 310 type Recv struct { 311 X **struct { 312 A []int `form:"a"` 313 B int32 `form:"b"` 314 C *[]uint16 `form:"c,required"` 315 D *float32 `form:"d"` 316 } 317 Y bool `form:"y,required"` 318 Z *int64 `form:"z"` 319 } 320 values := make(url.Values) 321 values.Add("a", "11") 322 values.Add("a", "12") 323 values.Add("b", "-21") 324 values.Add("c", "31") 325 values.Add("c", "32") 326 values.Add("d", "41") 327 values.Add("d", "42") 328 values.Add("y", "1") 329 for _, f := range []httpbody.Files{nil, { 330 "f1": []httpbody.File{ 331 httpbody.NewFile("txt", strings.NewReader("f11 text.")), 332 }, 333 }} { 334 contentType, bodyReader := httpbody.NewFormBody2(values, f) 335 header := make(http.Header) 336 header.Set("Content-Type", contentType) 337 req := newRequest("", header, nil, bodyReader) 338 recv := new(Recv) 339 binder := binding.New(nil) 340 err := binder.BindAndValidate(recv, req, nil) 341 assert.NoError(t, err) 342 assert.Equal(t, []int{11, 12}, (**recv.X).A) 343 assert.Equal(t, int32(-21), (**recv.X).B) 344 assert.Equal(t, &[]uint16{31, 32}, (**recv.X).C) 345 assert.Equal(t, float32(41), *(**recv.X).D) 346 assert.Equal(t, true, recv.Y) 347 assert.Equal(t, (*int64)(nil), recv.Z) 348 } 349 } 350 351 func TestJSON(t *testing.T) { 352 // binding.ResetJSONUnmarshaler(false, json.Unmarshal) 353 type metric string 354 type count int32 355 type ZS struct { 356 Z *int64 357 } 358 type Recv struct { 359 X **struct { 360 A []string `json:"a"` 361 B int32 `json:""` 362 C *[]uint16 `json:",required"` 363 D *float32 `json:"d"` 364 E metric `json:"e"` 365 F count `json:"f"` 366 M map[string]string `json:"m"` 367 } 368 Y string `json:"y,required"` 369 ZS 370 } 371 372 bodyReader := strings.NewReader(`{ 373 "X": { 374 "a": ["a1","a2"], 375 "B": 21, 376 "C": [31,32], 377 "d": 41, 378 "e": "qps", 379 "f": 100, 380 "m": {"a":"x"} 381 }, 382 "Z": 6 383 }`) 384 385 header := make(http.Header) 386 header.Set("Content-Type", "application/json") 387 req := newRequest("", header, nil, bodyReader) 388 recv := new(Recv) 389 binder := binding.New(nil) 390 err := binder.BindAndValidate(recv, req, nil) 391 assert.Error(t, err) 392 assert.Equal(t, &binding.Error{ErrType: "binding", FailField: "y", Msg: "missing required parameter"}, err) 393 assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A) 394 assert.Equal(t, int32(21), (**recv.X).B) 395 assert.Equal(t, &[]uint16{31, 32}, (**recv.X).C) 396 assert.Equal(t, float32(41), *(**recv.X).D) 397 assert.Equal(t, metric("qps"), (**recv.X).E) 398 assert.Equal(t, count(100), (**recv.X).F) 399 assert.Equal(t, map[string]string{"a": "x"}, (**recv.X).M) 400 assert.Equal(t, "", recv.Y) 401 assert.Equal(t, (int64)(6), *recv.Z) 402 } 403 404 func TestNonstruct(t *testing.T) { 405 bodyReader := strings.NewReader(`{ 406 "X": { 407 "a": ["a1","a2"], 408 "B": 21, 409 "C": [31,32], 410 "d": 41, 411 "e": "qps", 412 "f": 100 413 }, 414 "Z": 6 415 }`) 416 417 header := make(http.Header) 418 header.Set("Content-Type", "application/json") 419 req := newRequest("", header, nil, bodyReader) 420 var recv interface{} 421 binder := binding.New(nil) 422 err := binder.BindAndValidate(&recv, req, nil) 423 assert.NoError(t, err) 424 b, err := json.Marshal(recv) 425 assert.NoError(t, err) 426 t.Logf("%s", b) 427 428 bodyReader = strings.NewReader("b=334ddddd&token=yoMba34uspjVQEbhflgTRe2ceeDFUK32&type=url_verification") 429 header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") 430 req = newRequest("", header, nil, bodyReader) 431 recv = nil 432 err = binder.BindAndValidate(&recv, req, nil) 433 assert.NoError(t, err) 434 b, err = json.Marshal(recv) 435 assert.NoError(t, err) 436 t.Logf("%s", b) 437 } 438 439 func BenchmarkBindJSON(b *testing.B) { 440 type Recv struct { 441 X **struct { 442 A []string `json:"a"` 443 B int32 444 C *[]uint16 445 D *float32 `json:"d"` 446 } 447 Y string `json:"y"` 448 } 449 binder := binding.New(nil) 450 header := make(http.Header) 451 header.Set("Content-Type", "application/json") 452 test := func() { 453 bodyReader := strings.NewReader(`{ 454 "X": { 455 "a": ["a1","a2"], 456 "B": 21, 457 "C": [31,32], 458 "d": 41 459 }, 460 "y": "y1" 461 }`) 462 req := newRequest("", header, nil, bodyReader) 463 recv := new(Recv) 464 err := binder.Bind(recv, req, nil) 465 if err != nil { 466 b.Fatal(err) 467 } 468 } 469 test() 470 471 b.ReportAllocs() 472 b.ResetTimer() 473 474 for i := 0; i < b.N; i++ { 475 test() 476 } 477 } 478 479 func BenchmarkStdJSON(b *testing.B) { 480 type Recv struct { 481 X **struct { 482 A []string `json:"a"` 483 B int32 484 C *[]uint16 485 D *float32 `json:"d"` 486 } 487 Y string `json:"y"` 488 } 489 header := make(http.Header) 490 header.Set("Content-Type", "application/json") 491 492 b.ReportAllocs() 493 b.ResetTimer() 494 495 for i := 0; i < b.N; i++ { 496 bodyReader := strings.NewReader(`{ 497 "X": { 498 "a": ["a1","a2"], 499 "B": 21, 500 "C": [31,32], 501 "d": 41 502 }, 503 "y": "y1" 504 }`) 505 506 req := newRequest("", header, nil, bodyReader) 507 recv := new(Recv) 508 body, err := ioutil.ReadAll(req.Body) 509 req.Body.Close() 510 if err != nil { 511 b.Fatal(err) 512 } 513 err = json.Unmarshal(body, recv) 514 if err != nil { 515 b.Fatal(err) 516 } 517 } 518 } 519 520 type testPathParams struct{} 521 522 func (testPathParams) Get(name string) (string, bool) { 523 switch name { 524 case "a": 525 return "a1", true 526 case "b": 527 return "-21", true 528 case "c": 529 return "31", true 530 case "d": 531 return "41", true 532 case "y": 533 return "y1", true 534 case "name": 535 return "henrylee2cn", true 536 default: 537 return "", false 538 } 539 } 540 541 func TestPath(t *testing.T) { 542 type Recv struct { 543 X **struct { 544 A []string `path:"a"` 545 B int32 `path:"b"` 546 C *[]uint16 `path:"c,required"` 547 D *float32 `path:"d"` 548 } 549 Y string `path:"y,required"` 550 Z *int64 551 } 552 553 req := newRequest("", nil, nil, nil) 554 recv := new(Recv) 555 binder := binding.New(nil) 556 err := binder.BindAndValidate(recv, req, new(testPathParams)) 557 assert.NoError(t, err) 558 assert.Equal(t, []string{"a1"}, (**recv.X).A) 559 assert.Equal(t, int32(-21), (**recv.X).B) 560 assert.Equal(t, &[]uint16{31}, (**recv.X).C) 561 assert.Equal(t, float32(41), *(**recv.X).D) 562 assert.Equal(t, "y1", recv.Y) 563 assert.Equal(t, (*int64)(nil), recv.Z) 564 } 565 566 type testPathParams2 struct{} 567 568 func (testPathParams2) Get(name string) (string, bool) { 569 switch name { 570 case "e": 571 return "123", true 572 default: 573 return "", false 574 } 575 } 576 577 func TestDefault(t *testing.T) { 578 type S struct { 579 SS string `json:"ss"` 580 } 581 582 type Recv struct { 583 X **struct { 584 A []string `path:"a" json:"a"` 585 B int32 `path:"b" default:"32"` 586 C bool `json:"c" default:"true"` 587 D *float32 `default:"123.4"` 588 E *[]string `default:"['a','b','c','d,e,f']"` 589 F map[string]string `default:"{'a':'\"\\'1','\"b':'c','c':'2'}"` 590 G map[string]int64 `default:"{'a':1,'b':2,'c':3}"` 591 H map[string]float64 `default:"{'a':0.1,'b':1.2,'c':2.3}"` 592 I map[string]float64 `default:"{'\"a\"':0.1,'b':1.2,'c':2.3}"` 593 Empty string `default:""` 594 Null string `default:""` 595 CommaSpace string `default:",a:c "` 596 Dash string `default:"-"` 597 // InvalidInt int `default:"abc"` 598 // InvalidMap map[string]string `default:"abc"` 599 } 600 Y string `json:"y" default:"y1"` 601 Z int64 602 W string `json:"w"` 603 V []int64 `json:"u" default:"[1,2,3]"` 604 U []float32 `json:"u" default:"[1.1,2,3]"` 605 T *string `json:"t" default:"t1"` 606 S S `default:"{'ss':'test'}"` 607 O *S `default:"{'ss':'test2'}"` 608 Complex map[string][]map[string][]int64 `default:"{'a':[{'aa':[1,2,3], 'bb':[4,5]}],'b':[{}]}"` 609 } 610 611 bodyReader := strings.NewReader(`{ 612 "X": { 613 "a": ["a1","a2"] 614 }, 615 "Z": 6 616 }`) 617 618 // var nilMap map[string]string 619 header := make(http.Header) 620 header.Set("Content-Type", "application/json") 621 req := newRequest("", header, nil, bodyReader) 622 recv := new(Recv) 623 binder := binding.New(nil) 624 err := binder.BindAndValidate(recv, req, new(testPathParams2)) 625 assert.NoError(t, err) 626 assert.Equal(t, []string{"a1", "a2"}, (**recv.X).A) 627 assert.Equal(t, int32(32), (**recv.X).B) 628 assert.Equal(t, true, (**recv.X).C) 629 assert.Equal(t, float32(123.4), *(**recv.X).D) 630 assert.Equal(t, []string{"a", "b", "c", "d,e,f"}, *(**recv.X).E) 631 assert.Equal(t, map[string]string{"a": "\"'1", "\"b": "c", "c": "2"}, (**recv.X).F) 632 assert.Equal(t, map[string]int64{"a": 1, "b": 2, "c": 3}, (**recv.X).G) 633 assert.Equal(t, map[string]float64{"a": 0.1, "b": 1.2, "c": 2.3}, (**recv.X).H) 634 assert.Equal(t, map[string]float64{"\"a\"": 0.1, "b": 1.2, "c": 2.3}, (**recv.X).I) 635 assert.Equal(t, "", (**recv.X).Empty) 636 assert.Equal(t, "", (**recv.X).Null) 637 assert.Equal(t, ",a:c ", (**recv.X).CommaSpace) 638 assert.Equal(t, "-", (**recv.X).Dash) 639 // assert.Equal(t, 0, (**recv.X).InvalidInt) 640 // assert.Equal(t, nilMap, (**recv.X).InvalidMap) 641 assert.Equal(t, "y1", recv.Y) 642 assert.Equal(t, "t1", *recv.T) 643 assert.Equal(t, int64(6), recv.Z) 644 assert.Equal(t, []int64{1, 2, 3}, recv.V) 645 assert.Equal(t, []float32{1.1, 2, 3}, recv.U) 646 assert.Equal(t, S{SS: "test"}, recv.S) 647 assert.Equal(t, &S{SS: "test2"}, recv.O) 648 assert.Equal(t, map[string][]map[string][]int64{"a": {{"aa": {1, 2, 3}, "bb": []int64{4, 5}}}, "b": {map[string][]int64{}}}, recv.Complex) 649 } 650 651 func TestAuto(t *testing.T) { 652 type Recv struct { 653 A string `vd:"$!=''"` 654 B string 655 C string 656 D string `query:"D,required" form:"D,required"` 657 E string `cookie:"e" json:"e"` 658 } 659 query := make(url.Values) 660 query.Add("A", "a") 661 query.Add("B", "b") 662 query.Add("C", "c") 663 query.Add("D", "d-from-query") 664 contentType, bodyReader, err := httpbody.NewJSONBody(map[string]string{"e": "e-from-jsonbody"}) 665 assert.NoError(t, err) 666 header := make(http.Header) 667 header.Set("Content-Type", contentType) 668 req := newRequest("http://localhost/?"+query.Encode(), header, []*http.Cookie{ 669 {Name: "e", Value: "e-from-cookie"}, 670 }, bodyReader) 671 recv := new(Recv) 672 binder := binding.New(nil) 673 err = binder.BindAndValidate(recv, req, nil) 674 assert.NoError(t, err) 675 assert.Equal(t, "a", recv.A) 676 assert.Equal(t, "b", recv.B) 677 assert.Equal(t, "c", recv.C) 678 assert.Equal(t, "d-from-query", recv.D) 679 assert.Equal(t, "e-from-cookie", recv.E) 680 681 query = make(url.Values) 682 query.Add("D", "d-from-query") 683 form := make(url.Values) 684 form.Add("B", "b") 685 form.Add("C", "c") 686 form.Add("D", "d-from-form") 687 contentType, bodyReader = httpbody.NewFormBody2(form, nil) 688 header = make(http.Header) 689 header.Set("Content-Type", contentType) 690 req = newRequest("http://localhost/?"+query.Encode(), header, nil, bodyReader) 691 recv = new(Recv) 692 err = binder.Bind(recv, req, nil) 693 assert.NoError(t, err) 694 assert.Equal(t, "", recv.A) 695 assert.Equal(t, "b", recv.B) 696 assert.Equal(t, "c", recv.C) 697 assert.Equal(t, "d-from-form", recv.D) 698 err = binder.Validate(recv) 699 assert.EqualError(t, err, "validating: expr_path=A, cause=invalid") 700 } 701 702 func TestTypeUnmarshal(t *testing.T) { 703 type Recv struct { 704 A time.Time `form:"t1"` 705 B *time.Time `query:"t2"` 706 C []time.Time `query:"t2"` 707 } 708 query := make(url.Values) 709 query.Add("t2", "2019-09-04T14:05:24+08:00") 710 query.Add("t2", "2019-09-04T18:05:24+08:00") 711 form := make(url.Values) 712 form.Add("t1", "2019-09-03T18:05:24+08:00") 713 contentType, bodyReader := httpbody.NewFormBody2(form, nil) 714 header := make(http.Header) 715 header.Set("Content-Type", contentType) 716 req := newRequest("http://localhost/?"+query.Encode(), header, nil, bodyReader) 717 recv := new(Recv) 718 binder := binding.New(nil) 719 err := binder.BindAndValidate(recv, req, nil) 720 assert.NoError(t, err) 721 t1, err := time.Parse(time.RFC3339, "2019-09-03T18:05:24+08:00") 722 assert.NoError(t, err) 723 assert.Equal(t, t1, recv.A) 724 t21, err := time.Parse(time.RFC3339, "2019-09-04T14:05:24+08:00") 725 assert.NoError(t, err) 726 assert.Equal(t, t21, *recv.B) 727 t22, err := time.Parse(time.RFC3339, "2019-09-04T18:05:24+08:00") 728 assert.NoError(t, err) 729 assert.Equal(t, []time.Time{t21, t22}, recv.C) 730 t.Logf("%v", recv) 731 } 732 733 func TestOption(t *testing.T) { 734 type Recv struct { 735 X *struct { 736 C int `json:"c,required"` 737 D int `json:"d"` 738 } `json:"X"` 739 Y string `json:"y"` 740 } 741 header := make(http.Header) 742 header.Set("Content-Type", "application/json") 743 744 bodyReader := strings.NewReader(`{ 745 "X": { 746 "c": 21, 747 "d": 41 748 }, 749 "y": "y1" 750 }`) 751 req := newRequest("", header, nil, bodyReader) 752 recv := new(Recv) 753 binder := binding.New(nil) 754 err := binder.BindAndValidate(recv, req, nil) 755 assert.NoError(t, err) 756 assert.Equal(t, 21, recv.X.C) 757 assert.Equal(t, 41, recv.X.D) 758 assert.Equal(t, "y1", recv.Y) 759 760 bodyReader = strings.NewReader(`{ 761 "X": { 762 }, 763 "y": "y1" 764 }`) 765 req = newRequest("", header, nil, bodyReader) 766 recv = new(Recv) 767 binder = binding.New(nil) 768 err = binder.BindAndValidate(recv, req, nil) 769 assert.EqualError(t, err, "binding: expr_path=X.c, cause=missing required parameter") 770 assert.Equal(t, 0, recv.X.C) 771 assert.Equal(t, 0, recv.X.D) 772 assert.Equal(t, "y1", recv.Y) 773 774 bodyReader = strings.NewReader(`{ 775 "y": "y1" 776 }`) 777 req = newRequest("", header, nil, bodyReader) 778 recv = new(Recv) 779 binder = binding.New(nil) 780 err = binder.BindAndValidate(recv, req, nil) 781 assert.NoError(t, err) 782 assert.True(t, recv.X == nil) 783 assert.Equal(t, "y1", recv.Y) 784 785 type Recv2 struct { 786 X *struct { 787 C int `json:"c,required"` 788 D int `json:"d"` 789 } `json:"X,required"` 790 Y string `json:"y"` 791 } 792 bodyReader = strings.NewReader(`{ 793 "y": "y1" 794 }`) 795 req = newRequest("", header, nil, bodyReader) 796 recv2 := new(Recv2) 797 binder = binding.New(nil) 798 err = binder.BindAndValidate(recv2, req, nil) 799 assert.EqualError(t, err, "binding: expr_path=X, cause=missing required parameter") 800 assert.True(t, recv2.X == nil) 801 assert.Equal(t, "y1", recv2.Y) 802 } 803 804 func newRequest(u string, header http.Header, cookies []*http.Cookie, bodyReader io.Reader) *http.Request { 805 if header == nil { 806 header = make(http.Header) 807 } 808 var method = "GET" 809 var body io.ReadCloser 810 if bodyReader != nil { 811 method = "POST" 812 body = ioutil.NopCloser(bodyReader) 813 } 814 if u == "" { 815 u = "http://localhost" 816 } 817 urlObj, _ := url.Parse(u) 818 req := &http.Request{ 819 Method: method, 820 URL: urlObj, 821 Body: body, 822 Header: header, 823 } 824 for _, c := range cookies { 825 req.AddCookie(c) 826 } 827 return req 828 } 829 830 func TestQueryStringIssue(t *testing.T) { 831 type Timestamp struct { 832 time.Time 833 } 834 type Recv struct { 835 Name *string `query:"name"` 836 T *Timestamp `query:"t"` 837 } 838 req := newRequest("http://localhost:8080/?name=test", nil, nil, nil) 839 recv := new(Recv) 840 binder := binding.New(nil) 841 binder.SetLooseZeroMode(true) 842 err := binder.BindAndValidate(recv, req, nil) 843 assert.NoError(t, err) 844 assert.Equal(t, ameda.StringToStringPtr("test"), recv.Name) 845 assert.Equal(t, (*Timestamp)(nil), recv.T) 846 } 847 848 func TestQueryTypes(t *testing.T) { 849 type metric string 850 type count int32 851 type metrics []string 852 type filter struct { 853 Col1 string 854 } 855 856 type Recv struct { 857 A metric `vd:"$!=''"` 858 B count 859 C *count 860 D metrics `query:"D,required" form:"D,required"` 861 E metric `cookie:"e" json:"e"` 862 F filter `json:"f"` 863 } 864 query := make(url.Values) 865 query.Add("A", "qps") 866 query.Add("B", "123") 867 query.Add("C", "321") 868 query.Add("D", "dau") 869 query.Add("D", "dnu") 870 contentType, bodyReader, err := httpbody.NewJSONBody( 871 map[string]interface{}{ 872 "e": "e-from-jsonbody", 873 "f": filter{Col1: "abc"}, 874 }, 875 ) 876 assert.NoError(t, err) 877 header := make(http.Header) 878 header.Set("Content-Type", contentType) 879 req := newRequest("http://localhost/?"+query.Encode(), header, []*http.Cookie{ 880 {Name: "e", Value: "e-from-cookie"}, 881 }, bodyReader) 882 recv := new(Recv) 883 binder := binding.New(nil) 884 err = binder.BindAndValidate(recv, req, nil) 885 assert.NoError(t, err) 886 assert.Equal(t, metric("qps"), recv.A) 887 assert.Equal(t, count(123), recv.B) 888 assert.Equal(t, count(321), *recv.C) 889 assert.Equal(t, metrics{"dau", "dnu"}, recv.D) 890 assert.Equal(t, metric("e-from-cookie"), recv.E) 891 assert.Equal(t, filter{Col1: "abc"}, recv.F) 892 } 893 894 func TestNoTagIssue(t *testing.T) { 895 type x int 896 type T struct { 897 x 898 x2 x 899 a int 900 B int 901 } 902 req := newRequest("http://localhost:8080/?x=11&x2=12&a=1&B=2", nil, nil, nil) 903 recv := new(T) 904 binder := binding.New(nil) 905 binder.SetLooseZeroMode(true) 906 err := binder.BindAndValidate(recv, req, nil) 907 assert.NoError(t, err) 908 assert.Equal(t, x(0), recv.x) 909 assert.Equal(t, x(0), recv.x2) 910 assert.Equal(t, 0, recv.a) 911 assert.Equal(t, 2, recv.B) 912 } 913 914 func TestRegTypeUnmarshal(t *testing.T) { 915 type Q struct { 916 A int 917 B string 918 } 919 type T struct { 920 Q Q `query:"q"` 921 Qs []*Q `query:"qs"` 922 } 923 var values = url.Values{} 924 b, err := json.Marshal(Q{A: 2, B: "y"}) 925 assert.NoError(t, err) 926 values.Add("q", string(b)) 927 bs, err := json.Marshal([]Q{{A: 1, B: "x"}, {A: 2, B: "y"}}) 928 values.Add("qs", string(bs)) 929 req := newRequest("http://localhost:8080/?"+values.Encode(), nil, nil, nil) 930 recv := new(T) 931 binder := binding.New(nil) 932 err = binder.BindAndValidate(recv, req, nil) 933 if assert.NoError(t, err) { 934 assert.Equal(t, 2, recv.Q.A) 935 assert.Equal(t, "y", recv.Q.B) 936 assert.Equal(t, 1, recv.Qs[0].A) 937 assert.Equal(t, "x", recv.Qs[0].B) 938 assert.Equal(t, 2, recv.Qs[1].A) 939 assert.Equal(t, "y", recv.Qs[1].B) 940 } 941 } 942 943 func TestPathnameBUG(t *testing.T) { 944 type Currency struct { 945 CurrencyName *string `form:"currency_name,required" json:"currency_name,required" protobuf:"bytes,1,req,name=currency_name,json=currencyName" query:"currency_name,required"` 946 CurrencySymbol *string `form:"currency_symbol,required" json:"currency_symbol,required" protobuf:"bytes,2,req,name=currency_symbol,json=currencySymbol" query:"currency_symbol,required"` 947 SymbolPosition *int32 `form:"symbol_position,required" json:"symbol_position,required" protobuf:"varint,3,req,name=symbol_position,json=symbolPosition" query:"symbol_position,required"` 948 DecimalPlaces *int32 `form:"decimal_places,required" json:"decimal_places,required" protobuf:"varint,4,req,name=decimal_places,json=decimalPlaces" query:"decimal_places,required"` // 56x56 949 DecimalSymbol *string `form:"decimal_symbol,required" json:"decimal_symbol,required" protobuf:"bytes,5,req,name=decimal_symbol,json=decimalSymbol" query:"decimal_symbol,required"` 950 Separator *string `form:"separator,required" json:"separator,required" protobuf:"bytes,6,req,name=separator" query:"separator,required"` 951 SeparatorIndex *string `form:"separator_index,required" json:"separator_index,required" protobuf:"bytes,7,req,name=separator_index,json=separatorIndex" query:"separator_index,required"` 952 Between *string `form:"between,required" json:"between,required" protobuf:"bytes,8,req,name=between" query:"between,required"` 953 MinPrice *string `form:"min_price" json:"min_price,omitempty" protobuf:"bytes,9,opt,name=min_price,json=minPrice" query:"min_price"` 954 MaxPrice *string `form:"max_price" json:"max_price,omitempty" protobuf:"bytes,10,opt,name=max_price,json=maxPrice" query:"max_price"` 955 } 956 957 type CurrencyData struct { 958 Amount *string `form:"amount,required" json:"amount,required" protobuf:"bytes,1,req,name=amount" query:"amount,required"` 959 Currency *Currency `form:"currency,required" json:"currency,required" protobuf:"bytes,2,req,name=currency" query:"currency,required"` 960 } 961 962 type ExchangeCurrencyRequest struct { 963 PromotionRegion *string `form:"promotion_region,required" json:"promotion_region,required" protobuf:"bytes,1,req,name=promotion_region,json=promotionRegion" query:"promotion_region,required"` 964 Currency *CurrencyData `form:"currency,required" json:"currency,required" protobuf:"bytes,2,req,name=currency" query:"currency,required"` 965 Version *int32 `json:"version,omitempty" path:"version" protobuf:"varint,100,opt,name=version"` 966 } 967 968 z := &ExchangeCurrencyRequest{} 969 v := ameda.InitSampleValue(reflect.TypeOf(z), 10).Interface().(*ExchangeCurrencyRequest) 970 b, err := json.MarshalIndent(v, "", " ") 971 assert.NoError(t, err) 972 t.Log(string(b)) 973 header := make(http.Header) 974 header.Set("Content-Type", "application/json;charset=utf-8") 975 req := newRequest("http://localhost", header, nil, bytes.NewReader(b)) 976 recv := new(ExchangeCurrencyRequest) 977 binder := binding.New(nil) 978 err = binder.BindAndValidate(recv, req, nil) 979 assert.NoError(t, err) 980 981 assert.Equal(t, v, recv) 982 } 983 984 func TestPathnameBUG2(t *testing.T) { 985 type CurrencyData struct { 986 z int 987 Amount *string `form:"amount,required" json:"amount,required" protobuf:"bytes,1,req,name=amount" query:"amount,required"` 988 Name *string `form:"name,required" json:"name,required" protobuf:"bytes,2,req,name=name" query:"name,required"` 989 Symbol *string `form:"symbol" json:"symbol,omitempty" protobuf:"bytes,3,opt,name=symbol" query:"symbol"` 990 } 991 type TimeRange struct { 992 z int 993 StartTime *int64 `form:"start_time,required" json:"start_time,required" protobuf:"varint,1,req,name=start_time,json=startTime" query:"start_time,required"` 994 EndTime *int64 `form:"end_time,required" json:"end_time,required" protobuf:"varint,2,req,name=end_time,json=endTime" query:"end_time,required"` 995 } 996 type CreateFreeShippingRequest struct { 997 z int 998 PromotionName *string `form:"promotion_name,required" json:"promotion_name,required" protobuf:"bytes,1,req,name=promotion_name,json=promotionName" query:"promotion_name,required"` 999 PromotionRegion *string `form:"promotion_region,required" json:"promotion_region,required" protobuf:"bytes,2,req,name=promotion_region,json=promotionRegion" query:"promotion_region,required"` 1000 TimeRange *TimeRange `form:"time_range,required" json:"time_range,required" protobuf:"bytes,3,req,name=time_range,json=timeRange" query:"time_range,required"` 1001 PromotionBudget *CurrencyData `form:"promotion_budget,required" json:"promotion_budget,required" protobuf:"bytes,4,req,name=promotion_budget,json=promotionBudget" query:"promotion_budget,required"` 1002 Loaded_SellerIds []string `form:"loaded_Seller_ids" json:"loaded_Seller_ids,omitempty" protobuf:"bytes,5,rep,name=loaded_Seller_ids,json=loadedSellerIds" query:"loaded_Seller_ids"` 1003 Version *int32 `json:"version,omitempty" path:"version" protobuf:"varint,100,opt,name=version"` 1004 } 1005 1006 // z := &CreateFreeShippingRequest{} 1007 // v := ameda.InitSampleValue(reflect.TypeOf(z), 10).Interface().(*CreateFreeShippingRequest) 1008 // b, err := json.MarshalIndent(v, "", " ") 1009 // assert.NoError(t, err) 1010 // t.Log(string(b)) 1011 b := []byte(`{ 1012 "promotion_name": "mu", 1013 "promotion_region": "ID", 1014 "time_range": { 1015 "start_time": 1616420139, 1016 "end_time": 1616520139 1017 }, 1018 "promotion_budget": { 1019 "amount":"10000000", 1020 "name":"USD", 1021 "symbol":"$" 1022 }, 1023 "loaded_Seller_ids": [ 1024 "7493989780026655762","11111","111212121" 1025 ] 1026 }`) 1027 var v = new(CreateFreeShippingRequest) 1028 err := json.Unmarshal(b, v) 1029 assert.NoError(t, err) 1030 1031 header := make(http.Header) 1032 header.Set("Content-Type", "application/json;charset=utf-8") 1033 req := newRequest("http://localhost", header, nil, bytes.NewReader(b)) 1034 recv := new(CreateFreeShippingRequest) 1035 binder := binding.New(nil) 1036 err = binder.BindAndValidate(recv, req, nil) 1037 assert.NoError(t, err) 1038 1039 assert.Equal(t, v, recv) 1040 } 1041 1042 func TestRequiredBUG(t *testing.T) { 1043 type Currency struct { 1044 currencyName *string `vd:"$=='x'" form:"currency_name,required" json:"currency_name,required" protobuf:"bytes,1,req,name=currency_name,json=currencyName" query:"currency_name,required"` 1045 CurrencySymbol *string `vd:"$=='x'" form:"currency_symbol,required" json:"currency_symbol,required" protobuf:"bytes,2,req,name=currency_symbol,json=currencySymbol" query:"currency_symbol,required"` 1046 } 1047 1048 type CurrencyData struct { 1049 Amount *string `form:"amount,required" json:"amount,required" protobuf:"bytes,1,req,name=amount" query:"amount,required"` 1050 Slice []*Currency `form:"slice,required" json:"slice,required" protobuf:"bytes,2,req,name=slice" query:"slice,required"` 1051 Map map[string]*Currency `form:"map,required" json:"map,required" protobuf:"bytes,2,req,name=map" query:"map,required"` 1052 } 1053 1054 type ExchangeCurrencyRequest struct { 1055 PromotionRegion *string `form:"promotion_region,required" json:"promotion_region,required" protobuf:"bytes,1,req,name=promotion_region,json=promotionRegion" query:"promotion_region,required"` 1056 Currency *CurrencyData `form:"currency,required" json:"currency,required" protobuf:"bytes,2,req,name=currency" query:"currency,required"` 1057 } 1058 1059 z := &ExchangeCurrencyRequest{} 1060 // v := ameda.InitSampleValue(reflect.TypeOf(z), 10).Interface().(*ExchangeCurrencyRequest) 1061 b := []byte(`{ 1062 "promotion_region": "?", 1063 "currency": { 1064 "amount": "?", 1065 "slice": [ 1066 { 1067 "currency_symbol": "?" 1068 } 1069 ], 1070 "map": { 1071 "?": { 1072 "currency_name": "?" 1073 } 1074 } 1075 } 1076 }`) 1077 t.Log(string(b)) 1078 json.Unmarshal(b, z) 1079 header := make(http.Header) 1080 header.Set("Content-Type", "application/json;charset=utf-8") 1081 req := newRequest("http://localhost", header, nil, bytes.NewReader(b)) 1082 recv := new(ExchangeCurrencyRequest) 1083 binder := binding.New(nil) 1084 err := binder.BindAndValidate(recv, req, nil) 1085 assert.EqualError(t, err, "validating: expr_path=Currency.Slice[0].currencyName, cause=invalid") 1086 assert.Equal(t, z, recv) 1087 } 1088 1089 func TestIssue25(t *testing.T) { 1090 type Recv struct { 1091 A string 1092 } 1093 header := make(http.Header) 1094 header.Set("A", "from header") 1095 cookies := []*http.Cookie{ 1096 {Name: "A", Value: "from cookie"}, 1097 } 1098 req := newRequest("/1", header, cookies, nil) 1099 recv := new(Recv) 1100 binder := binding.New(nil) 1101 err := binder.BindAndValidate(recv, req, nil) 1102 assert.NoError(t, err) 1103 assert.Equal(t, "from cookie", recv.A) 1104 1105 header2 := make(http.Header) 1106 header2.Set("A", "from header") 1107 cookies2 := []*http.Cookie{} 1108 req2 := newRequest("/2", header2, cookies2, nil) 1109 recv2 := new(Recv) 1110 err2 := binder.BindAndValidate(recv2, req2, nil) 1111 assert.NoError(t, err2) 1112 assert.Equal(t, "from header", recv2.A) 1113 } 1114 1115 func TestIssue26(t *testing.T) { 1116 type Recv struct { 1117 Type string `json:"type,required" vd:"($=='update_target_threshold' && (TargetThreshold)$!='-1') || ($=='update_status' && (Status)$!='-1')"` 1118 RuleName string `json:"rule_name,required" vd:"regexp('^rule[0-9]+$')"` 1119 TargetThreshold string `json:"target_threshold" vd:"regexp('^-?[0-9]+(\\.[0-9]+)?$')"` 1120 Status string `json:"status" vd:"$=='0' || $=='1'"` 1121 Operator string `json:"operator,required" vd:"len($)>0"` 1122 } 1123 1124 b := []byte(`{ 1125 "status": "1", 1126 "adv": "11520", 1127 "target_deep_external_action": "39", 1128 "package": "test.bytedance.com", 1129 "previous_target_threshold": "0.6", 1130 "deep_external_action": "675", 1131 "rule_name": "rule2", 1132 "deep_bid_type": "54", 1133 "modify_time": "2021-08-24:14:35:20", 1134 "aid": "111", 1135 "operator": "yanghaoze", 1136 "external_action": "76", 1137 "target_threshold": "0.1", 1138 "type": "update_status" 1139 }`) 1140 1141 recv := new(Recv) 1142 err := json.Unmarshal(b, recv) 1143 assert.NoError(t, err) 1144 err = vd.Validate(&recv, true) 1145 assert.NoError(t, err) 1146 t.Log(spew.Sdump(recv)) 1147 1148 header := make(http.Header) 1149 header.Set("Content-Type", "application/json") 1150 header.Set("A", "from header") 1151 cookies := []*http.Cookie{ 1152 {Name: "A", Value: "from cookie"}, 1153 } 1154 1155 req := newRequest("/1", header, cookies, bytes.NewReader(b)) 1156 binder := binding.New(nil) 1157 recv2 := new(Recv) 1158 err = binder.BindAndValidate(&recv2, req, nil) 1159 assert.NoError(t, err) 1160 t.Log(spew.Sdump(recv2)) 1161 assert.Equal(t, recv, recv2) 1162 } 1163 1164 func TestDefault2(t *testing.T) { 1165 type Recv struct { 1166 X **struct { 1167 Dash string `default:"xxxx"` 1168 } 1169 } 1170 bodyReader := strings.NewReader(`{ 1171 "X": { 1172 "Dash": "hello Dash" 1173 } 1174 }`) 1175 header := make(http.Header) 1176 header.Set("Content-Type", "application/json") 1177 req := newRequest("", header, nil, bodyReader) 1178 recv := new(Recv) 1179 binder := binding.New(nil) 1180 err := binder.BindAndValidate(recv, req, new(testPathParams2)) 1181 assert.NoError(t, err) 1182 assert.Equal(t, "hello Dash", (**recv.X).Dash) 1183 } 1184 1185 func TestVdTagRecursion(t *testing.T) { 1186 type Node struct { 1187 N1 *Node 1188 N2 *Node 1189 N3 *Node 1190 } 1191 recv := &Node{} 1192 req, _ := http.NewRequest("get", "http://localhost/", bytes.NewReader([]byte{})) 1193 start := time.Now() 1194 binder := binding.New(nil) 1195 err := binder.BindAndValidate(recv, req, new(testPathParams2)) 1196 assert.NoError(t, err) 1197 assert.Less(t, int64(time.Since(start)), int64(time.Second)) 1198 }