mirror of
https://github.com/labstack/echo.git
synced 2025-01-12 01:22:21 +02:00
Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to ValueBinder
This commit is contained in:
parent
ec92fedf21
commit
59d2eaa4ac
121
binder.go
121
binder.go
@ -1,6 +1,8 @@
|
|||||||
package echo
|
package echo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -52,8 +54,11 @@ import (
|
|||||||
* time
|
* time
|
||||||
* duration
|
* duration
|
||||||
* BindUnmarshaler() interface
|
* BindUnmarshaler() interface
|
||||||
|
* TextUnmarshaler() interface
|
||||||
|
* JSONUnmarshaler() interface
|
||||||
* UnixTime() - converts unix time (integer) to time.Time
|
* UnixTime() - converts unix time (integer) to time.Time
|
||||||
* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
|
* UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
|
||||||
|
* UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time
|
||||||
* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
|
* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -321,6 +326,78 @@ func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshal
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface
|
||||||
|
func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
|
||||||
|
if b.failFast && b.errors != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := b.ValueFunc(sourceParam)
|
||||||
|
if tmp == "" {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
|
||||||
|
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustJSONUnmarshaler requires parameter value to exist to be bind to destination implementing json.Unmarshaler interface.
|
||||||
|
// Returns error when value does not exist
|
||||||
|
func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
|
||||||
|
if b.failFast && b.errors != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := b.ValueFunc(sourceParam)
|
||||||
|
if tmp == "" {
|
||||||
|
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
|
||||||
|
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface
|
||||||
|
func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
|
||||||
|
if b.failFast && b.errors != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := b.ValueFunc(sourceParam)
|
||||||
|
if tmp == "" {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
|
||||||
|
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustTextUnmarshaler requires parameter value to exist to be bind to destination implementing encoding.TextUnmarshaler interface.
|
||||||
|
// Returns error when value does not exist
|
||||||
|
func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
|
||||||
|
if b.failFast && b.errors != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := b.ValueFunc(sourceParam)
|
||||||
|
if tmp == "" {
|
||||||
|
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
|
||||||
|
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// BindWithDelimiter binds parameter to destination by suitable conversion function.
|
// BindWithDelimiter binds parameter to destination by suitable conversion function.
|
||||||
// Delimiter is used before conversion to split parameter value to separate values
|
// Delimiter is used before conversion to split parameter value to separate values
|
||||||
func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
|
func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
|
||||||
@ -1161,7 +1238,7 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
|
|||||||
// Note:
|
// Note:
|
||||||
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
||||||
func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
|
func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
|
||||||
return b.unixTime(sourceParam, dest, false, false)
|
return b.unixTime(sourceParam, dest, false, time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
|
// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
|
||||||
@ -1172,10 +1249,31 @@ func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder
|
|||||||
// Note:
|
// Note:
|
||||||
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
||||||
func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
|
func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
|
||||||
return b.unixTime(sourceParam, dest, true, false)
|
return b.unixTime(sourceParam, dest, true, time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision).
|
// UnixTimeMilli binds parameter to time.Time variable (in local Time corresponding to the given Unix time in millisecond precision).
|
||||||
|
//
|
||||||
|
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
||||||
|
func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
|
||||||
|
return b.unixTime(sourceParam, dest, false, time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustUnixTimeMilli requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
|
||||||
|
// to the given Unix time in millisecond precision). Returns error when value does not exist.
|
||||||
|
//
|
||||||
|
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
||||||
|
func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
|
||||||
|
return b.unixTime(sourceParam, dest, true, time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nanosecond precision).
|
||||||
//
|
//
|
||||||
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
|
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
|
||||||
// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
|
// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
|
||||||
@ -1185,7 +1283,7 @@ func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBi
|
|||||||
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
||||||
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
|
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
|
||||||
func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
|
func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
|
||||||
return b.unixTime(sourceParam, dest, false, true)
|
return b.unixTime(sourceParam, dest, false, time.Nanosecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
|
// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
|
||||||
@ -1199,10 +1297,10 @@ func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBi
|
|||||||
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
|
||||||
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
|
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
|
||||||
func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
|
func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
|
||||||
return b.unixTime(sourceParam, dest, true, true)
|
return b.unixTime(sourceParam, dest, true, time.Nanosecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder {
|
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, precision time.Duration) *ValueBinder {
|
||||||
if b.failFast && b.errors != nil {
|
if b.failFast && b.errors != nil {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
@ -1221,10 +1319,13 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNano {
|
switch precision {
|
||||||
*dest = time.Unix(0, n)
|
case time.Second:
|
||||||
} else {
|
|
||||||
*dest = time.Unix(n, 0)
|
*dest = time.Unix(n, 0)
|
||||||
|
case time.Millisecond:
|
||||||
|
*dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows
|
||||||
|
case time.Nanosecond:
|
||||||
|
*dest = time.Unix(0, n)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
274
binder_test.go
274
binder_test.go
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -2187,6 +2188,188 @@ func TestValueBinder_BindUnmarshaler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestValueBinder_BindWithDelimiter_types(t *testing.T) {
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
name string
|
name string
|
||||||
@ -2529,6 +2712,97 @@ func TestValueBinder_UnixTime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestValueBinder_UnixTimeNano(t *testing.T) {
|
||||||
exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43.000000000+00:00") // => 1609180603
|
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
|
exampleTimeNano, _ := time.Parse(time.RFC3339Nano, "2020-12-28T18:36:43.123456789+00:00") // => 1609180603123456789
|
||||||
|
Loading…
Reference in New Issue
Block a user