// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors package echo import ( "encoding/json" "errors" "fmt" "github.com/stretchr/testify/assert" "io" "math/big" "net/http" "net/http/httptest" "strconv" "strings" "testing" "time" ) func createTestContext(URL string, body io.Reader, pathParams map[string]string) Context { e := New() req := httptest.NewRequest(http.MethodGet, URL, body) if body != nil { req.Header.Set(HeaderContentType, MIMEApplicationJSON) } rec := httptest.NewRecorder() c := e.NewContext(req, rec) if len(pathParams) > 0 { names := make([]string, 0) values := make([]string, 0) for name, value := range pathParams { names = append(names, name) values = append(values, value) } c.SetParamNames(names...) c.SetParamValues(values...) } return c } func TestBindingError_Error(t *testing.T) { err := NewBindingError("id", []string{"1", "nope"}, "bind failed", errors.New("internal error")) assert.EqualError(t, err, `code=400, message=bind failed, internal=internal error, field=id`) bErr := err.(*BindingError) assert.Equal(t, 400, bErr.Code) assert.Equal(t, "bind failed", bErr.Message) assert.Equal(t, errors.New("internal error"), bErr.Internal) assert.Equal(t, "id", bErr.Field) assert.Equal(t, []string{"1", "nope"}, bErr.Values) } func TestBindingError_ErrorJSON(t *testing.T) { err := NewBindingError("id", []string{"1", "nope"}, "bind failed", errors.New("internal error")) resp, _ := json.Marshal(err) assert.Equal(t, `{"field":"id","message":"bind failed"}`, string(resp)) } func TestPathParamsBinder(t *testing.T) { c := createTestContext("/api/user/999", nil, map[string]string{ "id": "1", "nr": "2", "slice": "3", }) b := PathParamsBinder(c) id := int64(99) nr := int64(88) var slice = make([]int64, 0) var notExisting = make([]int64, 0) err := b.Int64("id", &id). Int64("nr", &nr). Int64s("slice", &slice). Int64s("not_existing", ¬Existing). BindError() assert.NoError(t, err) assert.Equal(t, int64(1), id) assert.Equal(t, int64(2), nr) assert.Equal(t, []int64{3}, slice) // binding params to slice does not make sense but it should not panic either assert.Equal(t, []int64{}, notExisting) // binding params to slice does not make sense but it should not panic either } func TestQueryParamsBinder_FailFast(t *testing.T) { var testCases = []struct { name string whenURL string givenFailFast bool expectError []string }{ { name: "ok, FailFast=true stops at first error", whenURL: "/api/user/999?nr=en&id=nope", givenFailFast: true, expectError: []string{ `code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing "nope": invalid syntax, field=id`, }, }, { name: "ok, FailFast=false encounters all errors", whenURL: "/api/user/999?nr=en&id=nope", givenFailFast: false, expectError: []string{ `code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing "nope": invalid syntax, field=id`, `code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing "en": invalid syntax, field=nr`, }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, map[string]string{"id": "999"}) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) id := int64(99) nr := int64(88) errs := b.Int64("id", &id). Int64("nr", &nr). BindErrors() assert.Len(t, errs, len(tc.expectError)) for _, err := range errs { assert.Contains(t, tc.expectError, err.Error()) } }) } } func TestFormFieldBinder(t *testing.T) { e := New() body := `texta=foo&slice=5` req := httptest.NewRequest(http.MethodPost, "/api/search?id=1&nr=2&slice=3&slice=4", strings.NewReader(body)) req.Header.Set(HeaderContentLength, strconv.Itoa(len(body))) req.Header.Set(HeaderContentType, MIMEApplicationForm) rec := httptest.NewRecorder() c := e.NewContext(req, rec) b := FormFieldBinder(c) var texta string id := int64(99) nr := int64(88) var slice = make([]int64, 0) var notExisting = make([]int64, 0) err := b. Int64s("slice", &slice). Int64("id", &id). Int64("nr", &nr). String("texta", &texta). Int64s("notExisting", ¬Existing). BindError() assert.NoError(t, err) assert.Equal(t, "foo", texta) assert.Equal(t, int64(1), id) assert.Equal(t, int64(2), nr) assert.Equal(t, []int64{5, 3, 4}, slice) assert.Equal(t, []int64{}, notExisting) } func TestValueBinder_errorStopsBinding(t *testing.T) { // this test documents "feature" that binding multiple params can change destination if it was binded before // failing parameter binding c := createTestContext("/api/user/999?id=1&nr=nope", nil, nil) b := QueryParamsBinder(c) id := int64(99) // will be changed before nr binding fails nr := int64(88) // will not be changed err := b.Int64("id", &id). Int64("nr", &nr). BindError() assert.EqualError(t, err, "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=nr") assert.Equal(t, int64(1), id) assert.Equal(t, int64(88), nr) } func TestValueBinder_BindError(t *testing.T) { c := createTestContext("/api/user/999?nr=en&id=nope", nil, nil) b := QueryParamsBinder(c) id := int64(99) nr := int64(88) err := b.Int64("id", &id). Int64("nr", &nr). BindError() assert.EqualError(t, err, "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=id") assert.Nil(t, b.errors) assert.Nil(t, b.BindError()) } func TestValueBinder_GetValues(t *testing.T) { var testCases = []struct { name string whenValuesFunc func(sourceParam string) []string expect []int64 expectError string }{ { name: "ok, default implementation", expect: []int64{1, 101}, }, { name: "ok, values returns nil", whenValuesFunc: func(sourceParam string) []string { return nil }, expect: []int64(nil), }, { name: "ok, values returns empty slice", whenValuesFunc: func(sourceParam string) []string { return []string{} }, expect: []int64(nil), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext("/search?nr=en&id=1&id=101", nil, nil) b := QueryParamsBinder(c) if tc.whenValuesFunc != nil { b.ValuesFunc = tc.whenValuesFunc } var IDs []int64 err := b.Int64s("id", &IDs).BindError() assert.Equal(t, tc.expect, IDs) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_CustomFuncWithError(t *testing.T) { c := createTestContext("/search?nr=en&id=1&id=101", nil, nil) b := QueryParamsBinder(c) id := int64(99) givenCustomFunc := func(values []string) []error { assert.Equal(t, []string{"1", "101"}, values) return []error{ errors.New("first error"), errors.New("second error"), } } err := b.CustomFunc("id", givenCustomFunc).BindError() assert.Equal(t, int64(99), id) assert.EqualError(t, err, "first error") } func TestValueBinder_CustomFunc(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenFuncErrors []error whenURL string expectParamValues []string expectValue interface{} expectErrors []string }{ { name: "ok, binds value", whenURL: "/search?nr=en&id=1&id=100", expectParamValues: []string{"1", "100"}, expectValue: int64(1000), }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nr=en", expectParamValues: []string{}, expectValue: int64(99), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?nr=en&id=1&id=100", expectParamValues: []string{"1", "100"}, expectValue: int64(99), expectErrors: []string{"previous error"}, }, { name: "nok, func returns errors", givenFuncErrors: []error{ errors.New("first error"), errors.New("second error"), }, whenURL: "/search?nr=en&id=1&id=100", expectParamValues: []string{"1", "100"}, expectValue: int64(99), expectErrors: []string{"first error", "second error"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } id := int64(99) givenCustomFunc := func(values []string) []error { assert.Equal(t, tc.expectParamValues, values) if tc.givenFuncErrors == nil { id = 1000 // emulated conversion and setting value return nil } return tc.givenFuncErrors } errs := b.CustomFunc("id", givenCustomFunc).BindErrors() assert.Equal(t, tc.expectValue, id) if tc.expectErrors != nil { assert.Len(t, errs, len(tc.expectErrors)) for _, err := range errs { assert.Contains(t, tc.expectErrors, err.Error()) } } else { assert.Nil(t, errs) } }) } } func TestValueBinder_MustCustomFunc(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenFuncErrors []error whenURL string expectParamValues []string expectValue interface{} expectErrors []string }{ { name: "ok, binds value", whenURL: "/search?nr=en&id=1&id=100", expectParamValues: []string{"1", "100"}, expectValue: int64(1000), }, { name: "nok, params values empty, returns error, value is not changed", whenURL: "/search?nr=en", expectParamValues: []string{}, expectValue: int64(99), expectErrors: []string{"code=400, message=required field value is empty, field=id"}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?nr=en&id=1&id=100", expectParamValues: []string{"1", "100"}, expectValue: int64(99), expectErrors: []string{"previous error"}, }, { name: "nok, func returns errors", givenFuncErrors: []error{ errors.New("first error"), errors.New("second error"), }, whenURL: "/search?nr=en&id=1&id=100", expectParamValues: []string{"1", "100"}, expectValue: int64(99), expectErrors: []string{"first error", "second error"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } id := int64(99) givenCustomFunc := func(values []string) []error { assert.Equal(t, tc.expectParamValues, values) if tc.givenFuncErrors == nil { id = 1000 // emulated conversion and setting value return nil } return tc.givenFuncErrors } errs := b.MustCustomFunc("id", givenCustomFunc).BindErrors() assert.Equal(t, tc.expectValue, id) if tc.expectErrors != nil { assert.Len(t, errs, len(tc.expectErrors)) for _, err := range errs { assert.Contains(t, tc.expectErrors, err.Error()) } } else { assert.Nil(t, errs) } }) } } func TestValueBinder_String(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue string expectError string }{ { name: "ok, binds value", whenURL: "/search?param=en¶m=de", expectValue: "en", }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nr=en", expectValue: "default", }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?nr=en&id=1&id=100", expectValue: "default", expectError: "previous error", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=en¶m=de", expectValue: "en", }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nr=en", expectValue: "default", expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?nr=en&id=1&id=100", expectValue: "default", expectError: "previous error", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := "default" var err error if tc.whenMust { err = b.MustString("param", &dest).BindError() } else { err = b.String("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Strings(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []string expectError string }{ { name: "ok, binds value", whenURL: "/search?param=en¶m=de", expectValue: []string{"en", "de"}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nr=en", expectValue: []string{"default"}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?nr=en&id=1&id=100", expectValue: []string{"default"}, expectError: "previous error", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=en¶m=de", expectValue: []string{"en", "de"}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nr=en", expectValue: []string{"default"}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?nr=en&id=1&id=100", expectValue: []string{"default"}, expectError: "previous error", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := []string{"default"} var err error if tc.whenMust { err = b.MustStrings("param", &dest).BindError() } else { err = b.Strings("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Int64_intValue(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue int64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=1¶m=100", expectValue: 1, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: 99, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: 99, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: 99, expectError: "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 1, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: 99, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 99, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: 99, expectError: "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := int64(99) var err error if tc.whenMust { err = b.MustInt64("param", &dest).BindError() } else { err = b.Int64("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Int_errorMessage(t *testing.T) { // int/uint (without byte size) has a little bit different error message so test these separately c := createTestContext("/search?param=nope", nil, nil) b := QueryParamsBinder(c).FailFast(false) destInt := 99 destUint := uint(98) errs := b.Int("param", &destInt).Uint("param", &destUint).BindErrors() assert.Equal(t, 99, destInt) assert.Equal(t, uint(98), destUint) assert.EqualError(t, errs[0], `code=400, message=failed to bind field value to int, internal=strconv.ParseInt: parsing "nope": invalid syntax, field=param`) assert.EqualError(t, errs[1], `code=400, message=failed to bind field value to uint, internal=strconv.ParseUint: parsing "nope": invalid syntax, field=param`) } func TestValueBinder_Uint64_uintValue(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue uint64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=1¶m=100", expectValue: 1, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: 99, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: 99, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: 99, expectError: "code=400, message=failed to bind field value to uint64, internal=strconv.ParseUint: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 1, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: 99, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 99, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: 99, expectError: "code=400, message=failed to bind field value to uint64, internal=strconv.ParseUint: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := uint64(99) var err error if tc.whenMust { err = b.MustUint64("param", &dest).BindError() } else { err = b.Uint64("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Int_Types(t *testing.T) { type target struct { int64 int64 mustInt64 int64 uint64 uint64 mustUint64 uint64 int32 int32 mustInt32 int32 uint32 uint32 mustUint32 uint32 int16 int16 mustInt16 int16 uint16 uint16 mustUint16 uint16 int8 int8 mustInt8 int8 uint8 uint8 mustUint8 uint8 byte byte mustByte byte int int mustInt int uint uint mustUint uint } types := []string{ "int64=1", "mustInt64=2", "uint64=3", "mustUint64=4", "int32=5", "mustInt32=6", "uint32=7", "mustUint32=8", "int16=9", "mustInt16=10", "uint16=11", "mustUint16=12", "int8=13", "mustInt8=14", "uint8=15", "mustUint8=16", "byte=17", "mustByte=18", "int=19", "mustInt=20", "uint=21", "mustUint=22", } c := createTestContext("/search?"+strings.Join(types, "&"), nil, nil) b := QueryParamsBinder(c) dest := target{} err := b. Int64("int64", &dest.int64). MustInt64("mustInt64", &dest.mustInt64). Uint64("uint64", &dest.uint64). MustUint64("mustUint64", &dest.mustUint64). Int32("int32", &dest.int32). MustInt32("mustInt32", &dest.mustInt32). Uint32("uint32", &dest.uint32). MustUint32("mustUint32", &dest.mustUint32). Int16("int16", &dest.int16). MustInt16("mustInt16", &dest.mustInt16). Uint16("uint16", &dest.uint16). MustUint16("mustUint16", &dest.mustUint16). Int8("int8", &dest.int8). MustInt8("mustInt8", &dest.mustInt8). Uint8("uint8", &dest.uint8). MustUint8("mustUint8", &dest.mustUint8). Byte("byte", &dest.byte). MustByte("mustByte", &dest.mustByte). Int("int", &dest.int). MustInt("mustInt", &dest.mustInt). Uint("uint", &dest.uint). MustUint("mustUint", &dest.mustUint). BindError() assert.NoError(t, err) assert.Equal(t, int64(1), dest.int64) assert.Equal(t, int64(2), dest.mustInt64) assert.Equal(t, uint64(3), dest.uint64) assert.Equal(t, uint64(4), dest.mustUint64) assert.Equal(t, int32(5), dest.int32) assert.Equal(t, int32(6), dest.mustInt32) assert.Equal(t, uint32(7), dest.uint32) assert.Equal(t, uint32(8), dest.mustUint32) assert.Equal(t, int16(9), dest.int16) assert.Equal(t, int16(10), dest.mustInt16) assert.Equal(t, uint16(11), dest.uint16) assert.Equal(t, uint16(12), dest.mustUint16) assert.Equal(t, int8(13), dest.int8) assert.Equal(t, int8(14), dest.mustInt8) assert.Equal(t, uint8(15), dest.uint8) assert.Equal(t, uint8(16), dest.mustUint8) assert.Equal(t, uint8(17), dest.byte) assert.Equal(t, uint8(18), dest.mustByte) assert.Equal(t, 19, dest.int) assert.Equal(t, 20, dest.mustInt) assert.Equal(t, uint(21), dest.uint) assert.Equal(t, uint(22), dest.mustUint) } func TestValueBinder_Int64s_intsValue(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []int64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=1¶m=2¶m=1", expectValue: []int64{1, 2, 1}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []int64{99}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: []int64{99}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []int64{99}, expectError: "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1¶m=2¶m=1", expectValue: []int64{1, 2, 1}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []int64{99}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []int64{99}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []int64{99}, expectError: "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := []int64{99} // when values are set with bind - contents before bind is gone var err error if tc.whenMust { err = b.MustInt64s("param", &dest).BindError() } else { err = b.Int64s("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Uint64s_uintsValue(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []uint64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=1¶m=2¶m=1", expectValue: []uint64{1, 2, 1}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []uint64{99}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: []uint64{99}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []uint64{99}, expectError: "code=400, message=failed to bind field value to uint64, internal=strconv.ParseUint: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1¶m=2¶m=1", expectValue: []uint64{1, 2, 1}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []uint64{99}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []uint64{99}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []uint64{99}, expectError: "code=400, message=failed to bind field value to uint64, internal=strconv.ParseUint: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := []uint64{99} // when values are set with bind - contents before bind is gone var err error if tc.whenMust { err = b.MustUint64s("param", &dest).BindError() } else { err = b.Uint64s("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Ints_Types(t *testing.T) { type target struct { int64 []int64 mustInt64 []int64 uint64 []uint64 mustUint64 []uint64 int32 []int32 mustInt32 []int32 uint32 []uint32 mustUint32 []uint32 int16 []int16 mustInt16 []int16 uint16 []uint16 mustUint16 []uint16 int8 []int8 mustInt8 []int8 uint8 []uint8 mustUint8 []uint8 int []int mustInt []int uint []uint mustUint []uint } types := []string{ "int64=1", "mustInt64=2", "uint64=3", "mustUint64=4", "int32=5", "mustInt32=6", "uint32=7", "mustUint32=8", "int16=9", "mustInt16=10", "uint16=11", "mustUint16=12", "int8=13", "mustInt8=14", "uint8=15", "mustUint8=16", "int=19", "mustInt=20", "uint=21", "mustUint=22", } url := "/search?" for _, v := range types { url = url + "&" + v + "&" + v } c := createTestContext(url, nil, nil) b := QueryParamsBinder(c) dest := target{} err := b. Int64s("int64", &dest.int64). MustInt64s("mustInt64", &dest.mustInt64). Uint64s("uint64", &dest.uint64). MustUint64s("mustUint64", &dest.mustUint64). Int32s("int32", &dest.int32). MustInt32s("mustInt32", &dest.mustInt32). Uint32s("uint32", &dest.uint32). MustUint32s("mustUint32", &dest.mustUint32). Int16s("int16", &dest.int16). MustInt16s("mustInt16", &dest.mustInt16). Uint16s("uint16", &dest.uint16). MustUint16s("mustUint16", &dest.mustUint16). Int8s("int8", &dest.int8). MustInt8s("mustInt8", &dest.mustInt8). Uint8s("uint8", &dest.uint8). MustUint8s("mustUint8", &dest.mustUint8). Ints("int", &dest.int). MustInts("mustInt", &dest.mustInt). Uints("uint", &dest.uint). MustUints("mustUint", &dest.mustUint). BindError() assert.NoError(t, err) assert.Equal(t, []int64{1, 1}, dest.int64) assert.Equal(t, []int64{2, 2}, dest.mustInt64) assert.Equal(t, []uint64{3, 3}, dest.uint64) assert.Equal(t, []uint64{4, 4}, dest.mustUint64) assert.Equal(t, []int32{5, 5}, dest.int32) assert.Equal(t, []int32{6, 6}, dest.mustInt32) assert.Equal(t, []uint32{7, 7}, dest.uint32) assert.Equal(t, []uint32{8, 8}, dest.mustUint32) assert.Equal(t, []int16{9, 9}, dest.int16) assert.Equal(t, []int16{10, 10}, dest.mustInt16) assert.Equal(t, []uint16{11, 11}, dest.uint16) assert.Equal(t, []uint16{12, 12}, dest.mustUint16) assert.Equal(t, []int8{13, 13}, dest.int8) assert.Equal(t, []int8{14, 14}, dest.mustInt8) assert.Equal(t, []uint8{15, 15}, dest.uint8) assert.Equal(t, []uint8{16, 16}, dest.mustUint8) assert.Equal(t, []int{19, 19}, dest.int) assert.Equal(t, []int{20, 20}, dest.mustInt) assert.Equal(t, []uint{21, 21}, dest.uint) assert.Equal(t, []uint{22, 22}, dest.mustUint) } func TestValueBinder_Ints_Types_FailFast(t *testing.T) { // FailFast() should stop parsing and return early errTmpl := "code=400, message=failed to bind field value to %v, internal=strconv.Parse%v: parsing \"nope\": invalid syntax, field=param" c := createTestContext("/search?param=1¶m=nope¶m=2", nil, nil) var dest64 []int64 err := QueryParamsBinder(c).FailFast(true).Int64s("param", &dest64).BindError() assert.Equal(t, []int64(nil), dest64) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "int64", "Int")) var dest32 []int32 err = QueryParamsBinder(c).FailFast(true).Int32s("param", &dest32).BindError() assert.Equal(t, []int32(nil), dest32) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "int32", "Int")) var dest16 []int16 err = QueryParamsBinder(c).FailFast(true).Int16s("param", &dest16).BindError() assert.Equal(t, []int16(nil), dest16) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "int16", "Int")) var dest8 []int8 err = QueryParamsBinder(c).FailFast(true).Int8s("param", &dest8).BindError() assert.Equal(t, []int8(nil), dest8) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "int8", "Int")) var dest []int err = QueryParamsBinder(c).FailFast(true).Ints("param", &dest).BindError() assert.Equal(t, []int(nil), dest) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "int", "Int")) var destu64 []uint64 err = QueryParamsBinder(c).FailFast(true).Uint64s("param", &destu64).BindError() assert.Equal(t, []uint64(nil), destu64) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "uint64", "Uint")) var destu32 []uint32 err = QueryParamsBinder(c).FailFast(true).Uint32s("param", &destu32).BindError() assert.Equal(t, []uint32(nil), destu32) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "uint32", "Uint")) var destu16 []uint16 err = QueryParamsBinder(c).FailFast(true).Uint16s("param", &destu16).BindError() assert.Equal(t, []uint16(nil), destu16) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "uint16", "Uint")) var destu8 []uint8 err = QueryParamsBinder(c).FailFast(true).Uint8s("param", &destu8).BindError() assert.Equal(t, []uint8(nil), destu8) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "uint8", "Uint")) var destu []uint err = QueryParamsBinder(c).FailFast(true).Uints("param", &destu).BindError() assert.Equal(t, []uint(nil), destu) assert.EqualError(t, err, fmt.Sprintf(errTmpl, "uint", "Uint")) } func TestValueBinder_Bool(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue bool expectError string }{ { name: "ok, binds value", whenURL: "/search?param=true¶m=1", expectValue: true, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: false, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: false, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: false, expectError: "code=400, message=failed to bind field value to bool, internal=strconv.ParseBool: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: true, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: false, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: false, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: false, expectError: "code=400, message=failed to bind field value to bool, internal=strconv.ParseBool: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := false var err error if tc.whenMust { err = b.MustBool("param", &dest).BindError() } else { err = b.Bool("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Bools(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []bool expectError string }{ { name: "ok, binds value", whenURL: "/search?param=true¶m=false¶m=1¶m=0", expectValue: []bool{true, false, true, false}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []bool(nil), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenURL: "/search?param=1¶m=100", expectValue: []bool(nil), expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=true¶m=nope¶m=100", expectValue: []bool(nil), expectError: "code=400, message=failed to bind field value to bool, internal=strconv.ParseBool: parsing \"nope\": invalid syntax, field=param", }, { name: "nok, conversion fails fast, value is not changed", givenFailFast: true, whenURL: "/search?param=true¶m=nope¶m=100", expectValue: []bool(nil), expectError: "code=400, message=failed to bind field value to bool, internal=strconv.ParseBool: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=true¶m=false¶m=1¶m=0", expectValue: []bool{true, false, true, false}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []bool(nil), expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []bool(nil), expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []bool(nil), expectError: "code=400, message=failed to bind field value to bool, internal=strconv.ParseBool: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors var dest []bool var err error if tc.whenMust { err = b.MustBools("param", &dest).BindError() } else { err = b.Bools("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Float64(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue float64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=4.3¶m=1", expectValue: 4.3, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: 1.123, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: 1.123, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: 1.123, expectError: "code=400, message=failed to bind field value to float64, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=4.3¶m=100", expectValue: 4.3, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: 1.123, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 1.123, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: 1.123, expectError: "code=400, message=failed to bind field value to float64, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := 1.123 var err error if tc.whenMust { err = b.MustFloat64("param", &dest).BindError() } else { err = b.Float64("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Float64s(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []float64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=4.3¶m=0", expectValue: []float64{4.3, 0}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []float64(nil), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenURL: "/search?param=1¶m=100", expectValue: []float64(nil), expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []float64(nil), expectError: "code=400, message=failed to bind field value to float64, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, { name: "nok, conversion fails fast, value is not changed", givenFailFast: true, whenURL: "/search?param=0¶m=nope¶m=100", expectValue: []float64(nil), expectError: "code=400, message=failed to bind field value to float64, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=4.3¶m=0", expectValue: []float64{4.3, 0}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []float64(nil), expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []float64(nil), expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []float64(nil), expectError: "code=400, message=failed to bind field value to float64, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors var dest []float64 var err error if tc.whenMust { err = b.MustFloat64s("param", &dest).BindError() } else { err = b.Float64s("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Float32(t *testing.T) { var testCases = []struct { name string givenNoFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue float32 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=4.3¶m=1", expectValue: 4.3, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: 1.123, }, { name: "nok, previous errors fail fast without binding value", givenNoFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: 1.123, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: 1.123, expectError: "code=400, message=failed to bind field value to float32, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=4.3¶m=100", expectValue: 4.3, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: 1.123, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenNoFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 1.123, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: 1.123, expectError: "code=400, message=failed to bind field value to float32, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenNoFailFast) if tc.givenNoFailFast { b.errors = []error{errors.New("previous error")} } dest := float32(1.123) var err error if tc.whenMust { err = b.MustFloat32("param", &dest).BindError() } else { err = b.Float32("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Float32s(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []float32 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=4.3¶m=0", expectValue: []float32{4.3, 0}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []float32(nil), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenURL: "/search?param=1¶m=100", expectValue: []float32(nil), expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []float32(nil), expectError: "code=400, message=failed to bind field value to float32, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, { name: "nok, conversion fails fast, value is not changed", givenFailFast: true, whenURL: "/search?param=0¶m=nope¶m=100", expectValue: []float32(nil), expectError: "code=400, message=failed to bind field value to float32, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=4.3¶m=0", expectValue: []float32{4.3, 0}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []float32(nil), expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []float32(nil), expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []float32(nil), expectError: "code=400, message=failed to bind field value to float32, internal=strconv.ParseFloat: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors var dest []float32 var err error if tc.whenMust { err = b.MustFloat32s("param", &dest).BindError() } else { err = b.Float32s("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Time(t *testing.T) { exampleTime, _ := time.Parse(time.RFC3339, "2020-12-23T09:45:31+02:00") var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool whenLayout string expectValue time.Time expectError string }{ { name: "ok, binds value", whenURL: "/search?param=2020-12-23T09:45:31%2B02:00¶m=2000-01-02T09:45:31%2B00:00", whenLayout: time.RFC3339, expectValue: exampleTime, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: time.Time{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=2020-12-23T09:45:31%2B02:00¶m=2000-01-02T09:45:31%2B00:00", whenLayout: time.RFC3339, expectValue: exampleTime, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: time.Time{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := time.Time{} var err error if tc.whenMust { err = b.MustTime("param", &dest, tc.whenLayout).BindError() } else { err = b.Time("param", &dest, tc.whenLayout).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Times(t *testing.T) { exampleTime, _ := time.Parse(time.RFC3339, "2020-12-23T09:45:31+02:00") exampleTime2, _ := time.Parse(time.RFC3339, "2000-01-02T09:45:31+00:00") var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool whenLayout string expectValue []time.Time expectError string }{ { name: "ok, binds value", whenURL: "/search?param=2020-12-23T09:45:31%2B02:00¶m=2000-01-02T09:45:31%2B00:00", whenLayout: time.RFC3339, expectValue: []time.Time{exampleTime, exampleTime2}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []time.Time(nil), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenURL: "/search?param=1¶m=100", expectValue: []time.Time(nil), expectError: "previous error", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=2020-12-23T09:45:31%2B02:00¶m=2000-01-02T09:45:31%2B00:00", whenLayout: time.RFC3339, expectValue: []time.Time{exampleTime, exampleTime2}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []time.Time(nil), expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []time.Time(nil), expectError: "previous error", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors layout := time.RFC3339 if tc.whenLayout != "" { layout = tc.whenLayout } var dest []time.Time var err error if tc.whenMust { err = b.MustTimes("param", &dest, layout).BindError() } else { err = b.Times("param", &dest, layout).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Duration(t *testing.T) { example := 42 * time.Second var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue time.Duration expectError string }{ { name: "ok, binds value", whenURL: "/search?param=42s¶m=1ms", expectValue: example, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: 0, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: 0, expectError: "previous error", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=42s¶m=1ms", expectValue: example, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: 0, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: 0, expectError: "previous error", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } var dest time.Duration var err error if tc.whenMust { err = b.MustDuration("param", &dest).BindError() } else { err = b.Duration("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_Durations(t *testing.T) { exampleDuration := 42 * time.Second exampleDuration2 := 1 * time.Millisecond var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []time.Duration expectError string }{ { name: "ok, binds value", whenURL: "/search?param=42s¶m=1ms", expectValue: []time.Duration{exampleDuration, exampleDuration2}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []time.Duration(nil), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenURL: "/search?param=1¶m=100", expectValue: []time.Duration(nil), expectError: "previous error", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=42s¶m=1ms", expectValue: []time.Duration{exampleDuration, exampleDuration2}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []time.Duration(nil), expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, givenBindErrors: []error{errors.New("previous error")}, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []time.Duration(nil), expectError: "previous error", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors var dest []time.Duration var err error if tc.whenMust { err = b.MustDurations("param", &dest).BindError() } else { err = b.Durations("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_BindUnmarshaler(t *testing.T) { exampleTime, _ := time.Parse(time.RFC3339, "2020-12-23T09:45:31+02:00") var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue Timestamp expectError string }{ { name: "ok, binds value", whenURL: "/search?param=2020-12-23T09:45:31%2B02:00¶m=2000-01-02T09:45:31%2B00:00", expectValue: Timestamp(exampleTime), }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: Timestamp{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: Timestamp{}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: Timestamp{}, expectError: "code=400, message=failed to bind field value to BindUnmarshaler interface, internal=parsing time \"nope\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"nope\" as \"2006\", field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=2020-12-23T09:45:31%2B02:00¶m=2000-01-02T09:45:31%2B00:00", expectValue: Timestamp(exampleTime), }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: Timestamp{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: Timestamp{}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: Timestamp{}, expectError: "code=400, message=failed to bind field value to BindUnmarshaler interface, internal=parsing time \"nope\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"nope\" as \"2006\", field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } var dest Timestamp var err error if tc.whenMust { err = b.MustBindUnmarshaler("param", &dest).BindError() } else { err = b.BindUnmarshaler("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_JSONUnmarshaler(t *testing.T) { example := big.NewInt(999) var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue big.Int expectError string }{ { name: "ok, binds value", whenURL: "/search?param=999¶m=998", expectValue: *example, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: big.Int{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: big.Int{}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=xxx", expectValue: big.Int{}, expectError: "code=400, message=failed to bind field value to json.Unmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=999¶m=998", expectValue: *example, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: big.Int{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=xxx", expectValue: big.Int{}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=xxx", expectValue: big.Int{}, expectError: "code=400, message=failed to bind field value to json.Unmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } var dest big.Int var err error if tc.whenMust { err = b.MustJSONUnmarshaler("param", &dest).BindError() } else { err = b.JSONUnmarshaler("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_TextUnmarshaler(t *testing.T) { example := big.NewInt(999) var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue big.Int expectError string }{ { name: "ok, binds value", whenURL: "/search?param=999¶m=998", expectValue: *example, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: big.Int{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: big.Int{}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=xxx", expectValue: big.Int{}, expectError: "code=400, message=failed to bind field value to encoding.TextUnmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=999¶m=998", expectValue: *example, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: big.Int{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=xxx", expectValue: big.Int{}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=xxx", expectValue: big.Int{}, expectError: "code=400, message=failed to bind field value to encoding.TextUnmarshaler interface, internal=math/big: cannot unmarshal \"nope\" into a *big.Int, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } var dest big.Int var err error if tc.whenMust { err = b.MustTextUnmarshaler("param", &dest).BindError() } else { err = b.TextUnmarshaler("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_BindWithDelimiter_types(t *testing.T) { var testCases = []struct { name string whenURL string expect interface{} }{ { name: "ok, strings", expect: []string{"1", "2", "1"}, }, { name: "ok, int64", expect: []int64{1, 2, 1}, }, { name: "ok, int32", expect: []int32{1, 2, 1}, }, { name: "ok, int16", expect: []int16{1, 2, 1}, }, { name: "ok, int8", expect: []int8{1, 2, 1}, }, { name: "ok, int", expect: []int{1, 2, 1}, }, { name: "ok, uint64", expect: []uint64{1, 2, 1}, }, { name: "ok, uint32", expect: []uint32{1, 2, 1}, }, { name: "ok, uint16", expect: []uint16{1, 2, 1}, }, { name: "ok, uint8", expect: []uint8{1, 2, 1}, }, { name: "ok, uint", expect: []uint{1, 2, 1}, }, { name: "ok, float64", expect: []float64{1, 2, 1}, }, { name: "ok, float32", expect: []float32{1, 2, 1}, }, { name: "ok, bool", whenURL: "/search?param=1,false¶m=true", expect: []bool{true, false, true}, }, { name: "ok, Duration", whenURL: "/search?param=1s,42s¶m=1ms", expect: []time.Duration{1 * time.Second, 42 * time.Second, 1 * time.Millisecond}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { URL := "/search?param=1,2¶m=1" if tc.whenURL != "" { URL = tc.whenURL } c := createTestContext(URL, nil, nil) b := QueryParamsBinder(c) switch tc.expect.(type) { case []string: var dest []string assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []int64: var dest []int64 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []int32: var dest []int32 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []int16: var dest []int16 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []int8: var dest []int8 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []int: var dest []int assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []uint64: var dest []uint64 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []uint32: var dest []uint32 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []uint16: var dest []uint16 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []uint8: var dest []uint8 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []uint: var dest []uint assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []float64: var dest []float64 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []float32: var dest []float32 assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []bool: var dest []bool assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) case []time.Duration: var dest []time.Duration assert.NoError(t, b.BindWithDelimiter("param", &dest, ",").BindError()) assert.Equal(t, tc.expect, dest) default: assert.Fail(t, "invalid type") } }) } } func TestValueBinder_BindWithDelimiter(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []int64 expectError string }{ { name: "ok, binds value", whenURL: "/search?param=1,2¶m=1", expectValue: []int64{1, 2, 1}, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: []int64(nil), }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: []int64(nil), expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []int64(nil), expectError: "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1,2¶m=1", expectValue: []int64{1, 2, 1}, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: []int64(nil), expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: []int64(nil), expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []int64(nil), expectError: "code=400, message=failed to bind field value to int64, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } var dest []int64 var err error if tc.whenMust { err = b.MustBindWithDelimiter("param", &dest, ",").BindError() } else { err = b.BindWithDelimiter("param", &dest, ",").BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestBindWithDelimiter_invalidType(t *testing.T) { c := createTestContext("/search?param=1¶m=100", nil, nil) b := QueryParamsBinder(c) var dest []BindUnmarshaler err := b.BindWithDelimiter("param", &dest, ",").BindError() assert.Equal(t, []BindUnmarshaler(nil), dest) assert.EqualError(t, err, "code=400, message=unsupported bind type, field=param") } func TestValueBinder_UnixTime(t *testing.T) { exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43+00:00") // => 1609180603 var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue time.Time expectError string }{ { name: "ok, binds value, unix time in seconds", whenURL: "/search?param=1609180603¶m=1609180604", expectValue: exampleTime, }, { name: "ok, binds value, unix time over int32 value", whenURL: "/search?param=2147483648¶m=1609180604", expectValue: time.Unix(2147483648, 0), }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: time.Time{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1609180603¶m=1609180604", expectValue: exampleTime, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: time.Time{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := time.Time{} var err error if tc.whenMust { err = b.MustUnixTime("param", &dest).BindError() } else { err = b.UnixTime("param", &dest).BindError() } assert.Equal(t, tc.expectValue.UnixNano(), dest.UnixNano()) assert.Equal(t, tc.expectValue.In(time.UTC), dest.In(time.UTC)) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_UnixTimeMilli(t *testing.T) { exampleTime, _ := time.Parse(time.RFC3339Nano, "2022-03-13T15:13:30.140000000+00:00") // => 1647184410140 var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue time.Time expectError string }{ { name: "ok, binds value, unix time in milliseconds", whenURL: "/search?param=1647184410140¶m=1647184410199", expectValue: exampleTime, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: time.Time{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1647184410140¶m=1647184410199", expectValue: exampleTime, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: time.Time{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := time.Time{} var err error if tc.whenMust { err = b.MustUnixTimeMilli("param", &dest).BindError() } else { err = b.UnixTimeMilli("param", &dest).BindError() } assert.Equal(t, tc.expectValue.UnixNano(), dest.UnixNano()) assert.Equal(t, tc.expectValue.In(time.UTC), dest.In(time.UTC)) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_UnixTimeNano(t *testing.T) { exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43.000000000+00:00") // => 1609180603 exampleTimeNano, _ := time.Parse(time.RFC3339Nano, "2020-12-28T18:36:43.123456789+00:00") // => 1609180603123456789 exampleTimeNanoBelowSec, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00.999999999+00:00") var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue time.Time expectError string }{ { name: "ok, binds value, unix time in nano seconds (sec precision)", whenURL: "/search?param=1609180603000000000¶m=1609180604", expectValue: exampleTime, }, { name: "ok, binds value, unix time in nano seconds", whenURL: "/search?param=1609180603123456789¶m=1609180604", expectValue: exampleTimeNano, }, { name: "ok, binds value, unix time in nano seconds (below 1 sec)", whenURL: "/search?param=999999999¶m=1609180604", expectValue: exampleTimeNanoBelowSec, }, { name: "ok, params values empty, value is not changed", whenURL: "/search?nope=1", expectValue: time.Time{}, }, { name: "nok, previous errors fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, { name: "ok (must), binds value", whenMust: true, whenURL: "/search?param=1609180603000000000¶m=1609180604", expectValue: exampleTime, }, { name: "ok (must), params values empty, returns error, value is not changed", whenMust: true, whenURL: "/search?nope=1", expectValue: time.Time{}, expectError: "code=400, message=required field value is empty, field=param", }, { name: "nok (must), previous errors fail fast without binding value", givenFailFast: true, whenMust: true, whenURL: "/search?param=1¶m=100", expectValue: time.Time{}, expectError: "previous error", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := time.Time{} var err error if tc.whenMust { err = b.MustUnixTimeNano("param", &dest).BindError() } else { err = b.UnixTimeNano("param", &dest).BindError() } assert.Equal(t, tc.expectValue.UnixNano(), dest.UnixNano()) assert.Equal(t, tc.expectValue.In(time.UTC), dest.In(time.UTC)) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func BenchmarkDefaultBinder_BindInt64_single(b *testing.B) { type Opts struct { Param int64 `query:"param"` } c := createTestContext("/search?param=1¶m=100", nil, nil) b.ReportAllocs() b.ResetTimer() binder := new(DefaultBinder) for i := 0; i < b.N; i++ { var dest Opts _ = binder.Bind(&dest, c) } } func BenchmarkValueBinder_BindInt64_single(b *testing.B) { c := createTestContext("/search?param=1¶m=100", nil, nil) b.ReportAllocs() b.ResetTimer() type Opts struct { Param int64 } binder := QueryParamsBinder(c) for i := 0; i < b.N; i++ { var dest Opts _ = binder.Int64("param", &dest.Param).BindError() } } func BenchmarkRawFunc_Int64_single(b *testing.B) { c := createTestContext("/search?param=1¶m=100", nil, nil) rawFunc := func(input string, defaultValue int64) (int64, bool) { if input == "" { return defaultValue, true } n, err := strconv.Atoi(input) if err != nil { return 0, false } return int64(n), true } b.ReportAllocs() b.ResetTimer() type Opts struct { Param int64 } for i := 0; i < b.N; i++ { var dest Opts if n, ok := rawFunc(c.QueryParam("param"), 1); ok { dest.Param = n } } } func BenchmarkDefaultBinder_BindInt64_10_fields(b *testing.B) { type Opts struct { Int64 int64 `query:"int64"` Int32 int32 `query:"int32"` Int16 int16 `query:"int16"` Int8 int8 `query:"int8"` String string `query:"string"` Uint64 uint64 `query:"uint64"` Uint32 uint32 `query:"uint32"` Uint16 uint16 `query:"uint16"` Uint8 uint8 `query:"uint8"` Strings []string `query:"strings"` } c := createTestContext("/search?int64=1&int32=2&int16=3&int8=4&string=test&uint64=5&uint32=6&uint16=7&uint8=8&strings=first&strings=second", nil, nil) b.ReportAllocs() b.ResetTimer() binder := new(DefaultBinder) for i := 0; i < b.N; i++ { var dest Opts _ = binder.Bind(&dest, c) if dest.Int64 != 1 { b.Fatalf("int64!=1") } } } func BenchmarkValueBinder_BindInt64_10_fields(b *testing.B) { type Opts struct { Int64 int64 `query:"int64"` Int32 int32 `query:"int32"` Int16 int16 `query:"int16"` Int8 int8 `query:"int8"` String string `query:"string"` Uint64 uint64 `query:"uint64"` Uint32 uint32 `query:"uint32"` Uint16 uint16 `query:"uint16"` Uint8 uint8 `query:"uint8"` Strings []string `query:"strings"` } c := createTestContext("/search?int64=1&int32=2&int16=3&int8=4&string=test&uint64=5&uint32=6&uint16=7&uint8=8&strings=first&strings=second", nil, nil) b.ReportAllocs() b.ResetTimer() binder := QueryParamsBinder(c) for i := 0; i < b.N; i++ { var dest Opts _ = binder. Int64("int64", &dest.Int64). Int32("int32", &dest.Int32). Int16("int16", &dest.Int16). Int8("int8", &dest.Int8). String("string", &dest.String). Uint64("int64", &dest.Uint64). Uint32("int32", &dest.Uint32). Uint16("int16", &dest.Uint16). Uint8("int8", &dest.Uint8). Strings("strings", &dest.Strings). BindError() if dest.Int64 != 1 { b.Fatalf("int64!=1") } } } func TestValueBinder_TimeError(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool whenLayout string expectValue time.Time expectError string }{ { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=parsing time \"nope\": extra text: \"nope\", field=param", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: time.Time{}, expectError: "code=400, message=failed to bind field value to Time, internal=parsing time \"nope\": extra text: \"nope\", field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } dest := time.Time{} var err error if tc.whenMust { err = b.MustTime("param", &dest, tc.whenLayout).BindError() } else { err = b.Time("param", &dest, tc.whenLayout).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_TimesError(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool whenLayout string expectValue []time.Time expectError string }{ { name: "nok, fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: []time.Time(nil), expectError: "code=400, message=failed to bind field value to Time, internal=parsing time \"1\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"1\" as \"2006\", field=param", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []time.Time(nil), expectError: "code=400, message=failed to bind field value to Time, internal=parsing time \"nope\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"nope\" as \"2006\", field=param", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []time.Time(nil), expectError: "code=400, message=failed to bind field value to Time, internal=parsing time \"nope\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"nope\" as \"2006\", field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors layout := time.RFC3339 if tc.whenLayout != "" { layout = tc.whenLayout } var dest []time.Time var err error if tc.whenMust { err = b.MustTimes("param", &dest, layout).BindError() } else { err = b.Times("param", &dest, layout).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_DurationError(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue time.Duration expectError string }{ { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: 0, expectError: "code=400, message=failed to bind field value to Duration, internal=time: invalid duration \"nope\", field=param", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: 0, expectError: "code=400, message=failed to bind field value to Duration, internal=time: invalid duration \"nope\", field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) if tc.givenFailFast { b.errors = []error{errors.New("previous error")} } var dest time.Duration var err error if tc.whenMust { err = b.MustDuration("param", &dest).BindError() } else { err = b.Duration("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } } func TestValueBinder_DurationsError(t *testing.T) { var testCases = []struct { name string givenFailFast bool givenBindErrors []error whenURL string whenMust bool expectValue []time.Duration expectError string }{ { name: "nok, fail fast without binding value", givenFailFast: true, whenURL: "/search?param=1¶m=100", expectValue: []time.Duration(nil), expectError: "code=400, message=failed to bind field value to Duration, internal=time: missing unit in duration \"1\", field=param", }, { name: "nok, conversion fails, value is not changed", whenURL: "/search?param=nope¶m=100", expectValue: []time.Duration(nil), expectError: "code=400, message=failed to bind field value to Duration, internal=time: invalid duration \"nope\", field=param", }, { name: "nok (must), conversion fails, value is not changed", whenMust: true, whenURL: "/search?param=nope¶m=100", expectValue: []time.Duration(nil), expectError: "code=400, message=failed to bind field value to Duration, internal=time: invalid duration \"nope\", field=param", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c := createTestContext(tc.whenURL, nil, nil) b := QueryParamsBinder(c).FailFast(tc.givenFailFast) b.errors = tc.givenBindErrors var dest []time.Duration var err error if tc.whenMust { err = b.MustDurations("param", &dest).BindError() } else { err = b.Durations("param", &dest).BindError() } assert.Equal(t, tc.expectValue, dest) if tc.expectError != "" { assert.EqualError(t, err, tc.expectError) } else { assert.NoError(t, err) } }) } }