1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-22 20:06:21 +02:00
echo/binder_test.go
toimtoimtoim 6ef5f77bf2 WIP: logger examples
WIP: make default logger implemented custom writer for jsonlike logs
WIP: improve examples
WIP: defaultErrorHandler use errors.As to unwrap errors. Update readme
WIP: default logger logs json, restore e.Start method
WIP: clean router.Match a bit
WIP: func types/fields have echo.Context has first element
WIP: remove yaml tags as functions etc can not be serialized anyway
WIP: change BindPathParams,BindQueryParams,BindHeaders from methods to functions and reverse arguments to be like DefaultBinder.Bind is
WIP: improved comments, logger now extracts status from error
WIP: go mod tidy
WIP: rebase with 4.5.0
WIP:
* removed todos.
* removed StartAutoTLS and StartH2CServer methods from `StartConfig`
* KeyAuth middleware errorhandler can swallow the error and resume next middleware
WIP: add RouterConfig.UseEscapedPathForMatching to use escaped path for matching request against routes
WIP: FIXMEs
WIP: upgrade golang-jwt/jwt to `v4`
WIP: refactor http methods to return RouteInfo
WIP: refactor static not creating multiple routes
WIP: refactor route and middleware adding functions not to return error directly
WIP: Use 401 for problematic/missing headers for key auth and JWT middleware (#1552, #1402).
> In summary, a 401 Unauthorized response should be used for missing or bad authentication
WIP: replace `HTTPError.SetInternal` with `HTTPError.WithInternal` so we could not mutate global error variables
WIP: add RouteInfo and RouteMatchType into Context what we could know from in middleware what route was matched and/or type of that match (200/404/405)
WIP: make notFoundHandler and methodNotAllowedHandler private. encourage that all errors be handled in Echo.HTTPErrorHandler
WIP: server cleanup ideas
WIP: routable.ForGroup
WIP: note about logger middleware
WIP: bind should not default values on second try. use crypto rand for better randomness
WIP: router add route as interface and returns info as interface
WIP: improve flaky test (remains still flaky)
WIP: add notes about bind default values
WIP: every route can have their own path params names
WIP: routerCreator and different tests
WIP: different things
WIP: remove route implementation
WIP: support custom method types
WIP: extractor tests
WIP: v5.0.x proposal
over v4.4.0
2021-10-02 18:36:42 +03:00

2759 lines
78 KiB
Go

// run tests as external package to get real feel for API
package echo
import (
"encoding/json"
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"io"
"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 {
params := make(PathParams, 0)
for name, value := range pathParams {
params = append(params, PathParam{
Name: name,
Value: value,
})
}
cc := c.(EditableContext)
cc.SetPathParams(params)
}
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, err := 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", &notExisting).
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", &notExisting).
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&param=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&param=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&param=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&param=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&param=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&param=100",
expectValue: 99,
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 99,
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 99,
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 99,
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=2&param=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&param=100",
expectValue: []int64{99},
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=2&param=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&param=100",
expectValue: []int64{99},
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=2&param=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&param=100",
expectValue: []uint64{99},
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=2&param=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&param=100",
expectValue: []uint64{99},
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=nope&param=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&param=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&param=100",
expectValue: false,
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: false,
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=false&param=1&param=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&param=100",
expectValue: []bool(nil),
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=true&param=nope&param=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&param=nope&param=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&param=false&param=1&param=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&param=100",
expectValue: []bool(nil),
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 1.123,
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 1.123,
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: []float64(nil),
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=nope&param=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&param=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&param=100",
expectValue: []float64(nil),
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 1.123,
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: 1.123,
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: []float32(nil),
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=nope&param=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&param=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&param=100",
expectValue: []float32(nil),
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: time.Time{},
expectError: "previous error",
},
{
name: "ok (must), binds value",
whenMust: true,
whenURL: "/search?param=2020-12-23T09:45:31%2B02:00&param=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&param=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&param=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&param=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&param=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&param=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&param=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&param=100",
expectValue: 0,
expectError: "previous error",
},
{
name: "ok (must), binds value",
whenMust: true,
whenURL: "/search?param=42s&param=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&param=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&param=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&param=100",
expectValue: []time.Duration(nil),
expectError: "previous error",
},
{
name: "ok (must), binds value",
whenMust: true,
whenURL: "/search?param=42s&param=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&param=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&param=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&param=100",
expectValue: Timestamp{},
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: Timestamp{},
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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_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&param=true",
expect: []bool{true, false, true},
},
{
name: "ok, Duration",
whenURL: "/search?param=1s,42s&param=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&param=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&param=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&param=100",
expectValue: []int64(nil),
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: []int64(nil),
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=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&param=1609180604",
expectValue: exampleTime,
},
{
name: "ok, binds value, unix time over int32 value",
whenURL: "/search?param=2147483648&param=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&param=100",
expectValue: time.Time{},
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: time.Time{},
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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_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&param=1609180604",
expectValue: exampleTime,
},
{
name: "ok, binds value, unix time in nano seconds",
whenURL: "/search?param=1609180603123456789&param=1609180604",
expectValue: exampleTimeNano,
},
{
name: "ok, binds value, unix time in nano seconds (below 1 sec)",
whenURL: "/search?param=999999999&param=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&param=100",
expectValue: time.Time{},
expectError: "previous error",
},
{
name: "nok, conversion fails, value is not changed",
whenURL: "/search?param=nope&param=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&param=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&param=100",
expectValue: time.Time{},
expectError: "previous error",
},
{
name: "nok (must), conversion fails, value is not changed",
whenMust: true,
whenURL: "/search?param=nope&param=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&param=100", nil, nil)
b.ReportAllocs()
b.ResetTimer()
binder := new(DefaultBinder)
for i := 0; i < b.N; i++ {
var dest Opts
_ = binder.Bind(c, &dest)
}
}
func BenchmarkValueBinder_BindInt64_single(b *testing.B) {
c := createTestContext("/search?param=1&param=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&param=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(c, &dest)
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")
}
}
}