Files
go-playground-form/decoder_test.go
T
dualfroz 8a7336f727 fix: nil-pointer panic from custom type function
A DecodeCustomTypeFunc returning nil to mean "leave this pointer
field unset" panicked in reflect.Value.Set, since reflect.ValueOf(nil)
is the invalid zero Value. Guard against a nil return before Set.

This is the panic from #68/#69: the earlier fix routed pointer fields
through the generic Ptr recursion instead of guarding nil, which
discarded a custom function's own populated pointer result and had
to be reverted. Guarding val == nil directly fixes the panic without
touching the case that broke.
2026-09-04 08:15:15 +02:00

2106 lines
54 KiB
Go

package form
import (
"errors"
"fmt"
"net/url"
"reflect"
"strings"
"testing"
"time"
. "github.com/go-playground/assert/v2"
)
// NOTES:
// - Run "go test" to run tests
// - Run "gocov test | gocov report" to report on test converage by file
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
//
// or
//
// -- may be a good idea to change to output path to somewherelike /tmp
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
//
//
// go test -cpuprofile cpu.out
// ./validator.test -test.bench=. -test.cpuprofile=cpu.prof
// go tool pprof validator.test cpu.prof
//
//
// go test -memprofile mem.out
func TestDecoderInt(t *testing.T) {
type TestInt struct {
Int int
Int8 int8
Int16 int16
Int32 int32
Int64 int64
IntPtr *int
Int8Ptr *int8
Int16Ptr *int16
Int32Ptr *int32
Int64Ptr *int64
IntArray []int
IntPtrArray []*int
IntArrayArray [][]int
IntPtrArrayArray [][]*int
IntMap map[int]int
IntPtrMap map[*int]*int
NoURLValue int
IntNoValues int
Int8NoValues int8
Int16NoValues int16
Int32NoValues int32
Int64NoValues int64
}
values := url.Values{
"Int": []string{"3"},
"Int8": []string{"3"},
"Int16": []string{"3"},
"Int32": []string{"3"},
"Int64": []string{"3"},
"IntPtr": []string{"3"},
"Int8Ptr": []string{"3"},
"Int16Ptr": []string{"3"},
"Int32Ptr": []string{"3"},
"Int64Ptr": []string{"3"},
"IntArray": []string{"1", "2", "3"},
"IntPtrArray[0]": []string{"1"},
"IntPtrArray[2]": []string{"3"},
"IntArrayArray[0][0]": []string{"1"},
"IntArrayArray[0][2]": []string{"3"},
"IntArrayArray[2][0]": []string{"1"},
"IntPtrArrayArray[0][0]": []string{"1"},
"IntPtrArrayArray[0][2]": []string{"3"},
"IntPtrArrayArray[2][0]": []string{"1"},
"IntMap[1]": []string{"3"},
"IntPtrMap[1]": []string{"3"},
}
var test TestInt
test.IntArray = make([]int, 4)
decoder := NewDecoder()
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.Int, int(3))
Equal(t, test.Int8, int8(3))
Equal(t, test.Int16, int16(3))
Equal(t, test.Int32, int32(3))
Equal(t, test.Int64, int64(3))
Equal(t, *test.IntPtr, int(3))
Equal(t, *test.Int8Ptr, int8(3))
Equal(t, *test.Int16Ptr, int16(3))
Equal(t, *test.Int32Ptr, int32(3))
Equal(t, *test.Int64Ptr, int64(3))
Equal(t, len(test.IntArray), 7)
Equal(t, test.IntArray[0], int(0))
Equal(t, test.IntArray[1], int(0))
Equal(t, test.IntArray[2], int(0))
Equal(t, test.IntArray[3], int(0))
Equal(t, test.IntArray[4], int(1))
Equal(t, test.IntArray[5], int(2))
Equal(t, test.IntArray[6], int(3))
Equal(t, len(test.IntPtrArray), 3)
Equal(t, *test.IntPtrArray[0], int(1))
Equal(t, test.IntPtrArray[1], nil)
Equal(t, *test.IntPtrArray[2], int(3))
Equal(t, len(test.IntArrayArray), 3)
Equal(t, len(test.IntArrayArray[0]), 3)
Equal(t, len(test.IntArrayArray[1]), 0)
Equal(t, len(test.IntArrayArray[2]), 1)
Equal(t, test.IntArrayArray[0][0], int(1))
Equal(t, test.IntArrayArray[0][1], int(0))
Equal(t, test.IntArrayArray[0][2], int(3))
Equal(t, test.IntArrayArray[2][0], int(1))
Equal(t, len(test.IntPtrArrayArray), 3)
Equal(t, len(test.IntPtrArrayArray[0]), 3)
Equal(t, len(test.IntPtrArrayArray[1]), 0)
Equal(t, len(test.IntPtrArrayArray[2]), 1)
Equal(t, *test.IntPtrArrayArray[0][0], int(1))
Equal(t, test.IntPtrArrayArray[0][1], nil)
Equal(t, *test.IntPtrArrayArray[0][2], int(3))
Equal(t, *test.IntPtrArrayArray[2][0], int(1))
Equal(t, len(test.IntMap), 1)
Equal(t, len(test.IntPtrMap), 1)
v, ok := test.IntMap[1]
Equal(t, ok, true)
Equal(t, v, int(3))
Equal(t, test.NoURLValue, int(0))
Equal(t, test.IntNoValues, int(0))
Equal(t, test.Int8NoValues, int8(0))
Equal(t, test.Int16NoValues, int16(0))
Equal(t, test.Int32NoValues, int32(0))
Equal(t, test.Int64NoValues, int64(0))
}
func TestDecoderUint(t *testing.T) {
type TestUint struct {
Uint uint
Uint8 uint8
Uint16 uint16
Uint32 uint32
Uint64 uint64
UintPtr *uint
Uint8Ptr *uint8
Uint16Ptr *uint16
Uint32Ptr *uint32
Uint64Ptr *uint64
UintArray []uint
UintPtrArray []*uint
UintArrayArray [][]uint
UintPtrArrayArray [][]*uint
UintMap map[uint]uint
UintPtrMap map[*uint]*uint
NoURLValue uint
UintNoValues uint
Uint8NoValues uint8
Uint16NoValues uint16
Uint32NoValues uint32
Uint64NoValues uint64
}
values := url.Values{
"Uint": []string{"3"},
"Uint8": []string{"3"},
"Uint16": []string{"3"},
"Uint32": []string{"3"},
"Uint64": []string{"3"},
"UintPtr": []string{"3"},
"Uint8Ptr": []string{"3"},
"Uint16Ptr": []string{"3"},
"Uint32Ptr": []string{"3"},
"Uint64Ptr": []string{"3"},
"UintArray": []string{"1", "2", "3"},
"UintPtrArray[0]": []string{"1"},
"UintPtrArray[2]": []string{"3"},
"UintArrayArray[0][0]": []string{"1"},
"UintArrayArray[0][2]": []string{"3"},
"UintArrayArray[2][0]": []string{"1"},
"UintPtrArrayArray[0][0]": []string{"1"},
"UintPtrArrayArray[0][2]": []string{"3"},
"UintPtrArrayArray[2][0]": []string{"1"},
"UintMap[1]": []string{"3"},
"UintPtrMap[1]": []string{"3"},
}
var test TestUint
test.UintArray = make([]uint, 4)
decoder := NewDecoder()
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.Uint, uint(3))
Equal(t, test.Uint8, uint8(3))
Equal(t, test.Uint16, uint16(3))
Equal(t, test.Uint32, uint32(3))
Equal(t, test.Uint64, uint64(3))
Equal(t, *test.UintPtr, uint(3))
Equal(t, *test.Uint8Ptr, uint8(3))
Equal(t, *test.Uint16Ptr, uint16(3))
Equal(t, *test.Uint32Ptr, uint32(3))
Equal(t, *test.Uint64Ptr, uint64(3))
Equal(t, len(test.UintArray), 7)
Equal(t, test.UintArray[0], uint(0))
Equal(t, test.UintArray[1], uint(0))
Equal(t, test.UintArray[2], uint(0))
Equal(t, test.UintArray[3], uint(0))
Equal(t, test.UintArray[4], uint(1))
Equal(t, test.UintArray[5], uint(2))
Equal(t, test.UintArray[6], uint(3))
Equal(t, len(test.UintPtrArray), 3)
Equal(t, *test.UintPtrArray[0], uint(1))
Equal(t, test.UintPtrArray[1], nil)
Equal(t, *test.UintPtrArray[2], uint(3))
Equal(t, len(test.UintArrayArray), 3)
Equal(t, len(test.UintArrayArray[0]), 3)
Equal(t, len(test.UintArrayArray[1]), 0)
Equal(t, len(test.UintArrayArray[2]), 1)
Equal(t, test.UintArrayArray[0][0], uint(1))
Equal(t, test.UintArrayArray[0][1], uint(0))
Equal(t, test.UintArrayArray[0][2], uint(3))
Equal(t, test.UintArrayArray[2][0], uint(1))
Equal(t, len(test.UintPtrArrayArray), 3)
Equal(t, len(test.UintPtrArrayArray[0]), 3)
Equal(t, len(test.UintPtrArrayArray[1]), 0)
Equal(t, len(test.UintPtrArrayArray[2]), 1)
Equal(t, *test.UintPtrArrayArray[0][0], uint(1))
Equal(t, test.UintPtrArrayArray[0][1], nil)
Equal(t, *test.UintPtrArrayArray[0][2], uint(3))
Equal(t, *test.UintPtrArrayArray[2][0], uint(1))
Equal(t, len(test.UintMap), 1)
Equal(t, len(test.UintPtrMap), 1)
v, ok := test.UintMap[1]
Equal(t, ok, true)
Equal(t, v, uint(3))
Equal(t, test.NoURLValue, uint(0))
Equal(t, test.UintNoValues, uint(0))
Equal(t, test.Uint8NoValues, uint8(0))
Equal(t, test.Uint16NoValues, uint16(0))
Equal(t, test.Uint32NoValues, uint32(0))
Equal(t, test.Uint64NoValues, uint64(0))
}
func TestDecoderString(t *testing.T) {
type TestString struct {
String string
StringPtr *string
StringArray []string
StringPtrArray []*string
StringArrayArray [][]string
StringPtrArrayArray [][]*string
StringMap map[string]string
StringPtrMap map[*string]*string
NoURLValue string
}
values := url.Values{
"String": []string{"3"},
"StringPtr": []string{"3"},
"StringArray": []string{"1", "2", "3"},
"StringPtrArray[0]": []string{"1"},
"StringPtrArray[2]": []string{"3"},
"StringArrayArray[0][0]": []string{"1"},
"StringArrayArray[0][2]": []string{"3"},
"StringArrayArray[2][0]": []string{"1"},
"StringPtrArrayArray[0][0]": []string{"1"},
"StringPtrArrayArray[0][2]": []string{"3"},
"StringPtrArrayArray[2][0]": []string{"1"},
"StringMap[1]": []string{"3"},
"StringPtrMap[1]": []string{"3"},
}
var test TestString
test.StringArray = make([]string, 4)
decoder := NewDecoder()
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.String, "3")
Equal(t, *test.StringPtr, "3")
Equal(t, len(test.StringArray), 7)
Equal(t, test.StringArray[0], "")
Equal(t, test.StringArray[1], "")
Equal(t, test.StringArray[2], "")
Equal(t, test.StringArray[3], "")
Equal(t, test.StringArray[4], "1")
Equal(t, test.StringArray[5], "2")
Equal(t, test.StringArray[6], "3")
Equal(t, len(test.StringPtrArray), 3)
Equal(t, *test.StringPtrArray[0], "1")
Equal(t, test.StringPtrArray[1], nil)
Equal(t, *test.StringPtrArray[2], "3")
Equal(t, len(test.StringArrayArray), 3)
Equal(t, len(test.StringArrayArray[0]), 3)
Equal(t, len(test.StringArrayArray[1]), 0)
Equal(t, len(test.StringArrayArray[2]), 1)
Equal(t, test.StringArrayArray[0][0], "1")
Equal(t, test.StringArrayArray[0][1], "")
Equal(t, test.StringArrayArray[0][2], "3")
Equal(t, test.StringArrayArray[2][0], "1")
Equal(t, len(test.StringPtrArrayArray), 3)
Equal(t, len(test.StringPtrArrayArray[0]), 3)
Equal(t, len(test.StringPtrArrayArray[1]), 0)
Equal(t, len(test.StringPtrArrayArray[2]), 1)
Equal(t, *test.StringPtrArrayArray[0][0], "1")
Equal(t, test.StringPtrArrayArray[0][1], nil)
Equal(t, *test.StringPtrArrayArray[0][2], "3")
Equal(t, *test.StringPtrArrayArray[2][0], "1")
Equal(t, len(test.StringMap), 1)
Equal(t, len(test.StringPtrMap), 1)
v, ok := test.StringMap["1"]
Equal(t, ok, true)
Equal(t, v, "3")
Equal(t, test.NoURLValue, "")
}
func TestDecoderFloat(t *testing.T) {
type TestFloat struct {
Float32 float32
Float32Ptr *float32
Float64 float64
Float64Ptr *float64
Float32Array []float32
Float32PtrArray []*float32
Float32ArrayArray [][]float32
Float32PtrArrayArray [][]*float32
Float32Map map[float32]float32
Float32PtrMap map[*float32]*float32
Float32NoValue float32
Float64NoValue float64
}
values := url.Values{
"Float32": []string{"3.3"},
"Float32Ptr": []string{"3.3"},
"Float64": []string{"3.3"},
"Float64Ptr": []string{"3.3"},
"Float32Array": []string{"1.1", "2.2", "3.3"},
"Float32PtrArray[0]": []string{"1.1"},
"Float32PtrArray[2]": []string{"3.3"},
"Float32ArrayArray[0][0]": []string{"1.1"},
"Float32ArrayArray[0][2]": []string{"3.3"},
"Float32ArrayArray[2][0]": []string{"1.1"},
"Float32PtrArrayArray[0][0]": []string{"1.1"},
"Float32PtrArrayArray[0][2]": []string{"3.3"},
"Float32PtrArrayArray[2][0]": []string{"1.1"},
"Float32Map[1.1]": []string{"3.3"},
"Float32PtrMap[1.1]": []string{"3.3"},
}
var test TestFloat
test.Float32Array = make([]float32, 4)
decoder := NewDecoder()
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.Float32, float32(3.3))
Equal(t, test.Float64, float64(3.3))
Equal(t, *test.Float32Ptr, float32(3.3))
Equal(t, *test.Float64Ptr, float64(3.3))
Equal(t, len(test.Float32Array), 7)
Equal(t, test.Float32Array[0], float32(0.0))
Equal(t, test.Float32Array[1], float32(0.0))
Equal(t, test.Float32Array[2], float32(0.0))
Equal(t, test.Float32Array[3], float32(0.0))
Equal(t, test.Float32Array[4], float32(1.1))
Equal(t, test.Float32Array[5], float32(2.2))
Equal(t, test.Float32Array[6], float32(3.3))
Equal(t, len(test.Float32PtrArray), 3)
Equal(t, *test.Float32PtrArray[0], float32(1.1))
Equal(t, test.Float32PtrArray[1], nil)
Equal(t, *test.Float32PtrArray[2], float32(3.3))
Equal(t, len(test.Float32ArrayArray), 3)
Equal(t, len(test.Float32ArrayArray[0]), 3)
Equal(t, len(test.Float32ArrayArray[1]), 0)
Equal(t, len(test.Float32ArrayArray[2]), 1)
Equal(t, test.Float32ArrayArray[0][0], float32(1.1))
Equal(t, test.Float32ArrayArray[0][1], float32(0.0))
Equal(t, test.Float32ArrayArray[0][2], float32(3.3))
Equal(t, test.Float32ArrayArray[2][0], float32(1.1))
Equal(t, len(test.Float32PtrArrayArray), 3)
Equal(t, len(test.Float32PtrArrayArray[0]), 3)
Equal(t, len(test.Float32PtrArrayArray[1]), 0)
Equal(t, len(test.Float32PtrArrayArray[2]), 1)
Equal(t, *test.Float32PtrArrayArray[0][0], float32(1.1))
Equal(t, test.Float32PtrArrayArray[0][1], nil)
Equal(t, *test.Float32PtrArrayArray[0][2], float32(3.3))
Equal(t, *test.Float32PtrArrayArray[2][0], float32(1.1))
Equal(t, len(test.Float32Map), 1)
Equal(t, len(test.Float32PtrMap), 1)
v, ok := test.Float32Map[float32(1.1)]
Equal(t, ok, true)
Equal(t, v, float32(3.3))
Equal(t, test.Float32NoValue, float32(0.0))
Equal(t, test.Float64NoValue, float64(0.0))
}
func TestDecoderBool(t *testing.T) {
type TestBool struct {
Bool bool
BoolPtr *bool
BoolPtrNil *bool
BoolPtrEmpty *bool
BoolArray []bool
BoolPtrArray []*bool
BoolArrayArray [][]bool
BoolPtrArrayArray [][]*bool
BoolMap map[bool]bool
BoolPtrMap map[*bool]*bool
NoURLValue bool
}
values := url.Values{
"Bool": []string{"true"},
"BoolPtr": []string{"true"},
"BoolPtrEmpty": []string{""},
"BoolArray": []string{"off", "t", "on"},
"BoolPtrArray[0]": []string{"true"},
"BoolPtrArray[2]": []string{"T"},
"BoolArrayArray[0][0]": []string{"TRUE"},
"BoolArrayArray[0][2]": []string{"True"},
"BoolArrayArray[2][0]": []string{"true"},
"BoolPtrArrayArray[0][0]": []string{"true"},
"BoolPtrArrayArray[0][2]": []string{"t"},
"BoolPtrArrayArray[2][0]": []string{"1"},
"BoolMap[true]": []string{"true"},
"BoolPtrMap[t]": []string{"true"},
}
var test TestBool
test.BoolArray = make([]bool, 4)
decoder := NewDecoder()
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.Bool, true)
Equal(t, *test.BoolPtr, true)
Equal(t, test.BoolPtrNil, nil)
NotEqual(t, test.BoolPtrEmpty, nil)
Equal(t, *test.BoolPtrEmpty, false)
Equal(t, len(test.BoolArray), 7)
Equal(t, test.BoolArray[0], false)
Equal(t, test.BoolArray[1], false)
Equal(t, test.BoolArray[2], false)
Equal(t, test.BoolArray[3], false)
Equal(t, test.BoolArray[4], false)
Equal(t, test.BoolArray[5], true)
Equal(t, test.BoolArray[6], true)
Equal(t, len(test.BoolPtrArray), 3)
Equal(t, *test.BoolPtrArray[0], true)
Equal(t, test.BoolPtrArray[1], nil)
Equal(t, *test.BoolPtrArray[2], true)
Equal(t, len(test.BoolArrayArray), 3)
Equal(t, len(test.BoolArrayArray[0]), 3)
Equal(t, len(test.BoolArrayArray[1]), 0)
Equal(t, len(test.BoolArrayArray[2]), 1)
Equal(t, test.BoolArrayArray[0][0], true)
Equal(t, test.BoolArrayArray[0][1], false)
Equal(t, test.BoolArrayArray[0][2], true)
Equal(t, test.BoolArrayArray[2][0], true)
Equal(t, len(test.BoolPtrArrayArray), 3)
Equal(t, len(test.BoolPtrArrayArray[0]), 3)
Equal(t, len(test.BoolPtrArrayArray[1]), 0)
Equal(t, len(test.BoolPtrArrayArray[2]), 1)
Equal(t, *test.BoolPtrArrayArray[0][0], true)
Equal(t, test.BoolPtrArrayArray[0][1], nil)
Equal(t, *test.BoolPtrArrayArray[0][2], true)
Equal(t, *test.BoolPtrArrayArray[2][0], true)
Equal(t, len(test.BoolMap), 1)
Equal(t, len(test.BoolPtrMap), 1)
v, ok := test.BoolMap[true]
Equal(t, ok, true)
Equal(t, v, true)
Equal(t, test.NoURLValue, false)
}
func TestDecoderEqualStructMapValue(t *testing.T) {
type PhoneStruct struct {
Number string
}
type PhoneMap map[string]string
type TestStruct struct {
PhoneStruct PhoneStruct `form:"Phone"`
PhoneMap PhoneMap `form:"Phone"`
}
testCases := []struct {
NamespacePrefix string
NamespaceSuffix string
Values url.Values
}{{
NamespacePrefix: ".",
Values: url.Values{
"Phone.Number": []string{"111"},
"Phone[Number]": []string{"222"},
},
}, {
NamespacePrefix: "[",
NamespaceSuffix: "]",
Values: url.Values{
"Phone[Number]": []string{"111"},
},
}}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("Namespace_%s%s", tc.NamespacePrefix, tc.NamespaceSuffix), func(t *testing.T) {
decoder := NewDecoder()
decoder.SetNamespacePrefix(tc.NamespacePrefix)
decoder.SetNamespaceSuffix(tc.NamespaceSuffix)
var test TestStruct
err := decoder.Decode(&test, tc.Values)
Equal(t, err, nil)
Equal(t, test.PhoneStruct.Number, "111")
if tc.NamespacePrefix == "." {
Equal(t, test.PhoneMap["Number"], "222")
} else {
Equal(t, test.PhoneMap["Number"], "111")
}
})
}
}
func TestDecoderStruct(t *testing.T) {
type Phone struct {
Number string
}
type TestMapKeys struct {
MapIfaceKey map[interface{}]string
MapFloat32Key map[float32]float32
MapFloat64Key map[float64]float64
MapNestedInt map[int]map[int]int
MapInt8 map[int8]int8
MapInt16 map[int16]int16
MapInt32 map[int32]int32
MapUint8 map[uint8]uint8
MapUint16 map[uint16]uint16
MapUint32 map[uint32]uint32
}
type TestStruct struct {
Name string `form:"name"`
Phone []Phone
PhonePtr []*Phone
NonNilPtr *Phone
Ignore string `form:"-"`
Anonymous struct {
Value string
Ignore string `form:"-"`
unexposed string
}
Time time.Time
TimePtr *time.Time
Invalid interface{}
ExistingMap map[string]string `form:"mp"`
MapNoValue map[int]int
TestMapKeys TestMapKeys
NilArray []string
TooSmallArray []string
TooSmallCapOKArray []string
ZeroLengthArray []string
TooSmallNumberedArray []string
TooSmallCapOKNumberedArray []string
BigEnoughNumberedArray []string
IfaceNonNil interface{}
IfaceInvalid interface{}
TimeMapKey map[time.Time]string
ExistingArray []string
ExistingArrayIndex []string
}
defaultValues := url.Values{
"name": []string{"joeybloggs"},
"Ignore": []string{"ignore"},
"Time": []string{"2016-01-02"},
"TimePtr": []string{"2016-01-02"},
"mp[key]": []string{"value"},
"NilArray": []string{"1", "2"},
"TooSmallArray": []string{"1", "2"},
"TooSmallCapOKArray": []string{"1", "2"},
"ZeroLengthArray": []string{},
"TooSmallNumberedArray[2]": []string{"2"},
"TooSmallCapOKNumberedArray[2]": []string{"2"},
"BigEnoughNumberedArray[2]": []string{"1"},
"TimeMapKey[2016-01-02]": []string{"time"},
"ExistingArray": []string{"arr2"},
"ExistingArrayIndex[1]": []string{"arr2"},
}
testCases := []struct {
NamespacePrefix string
NamespaceSuffix string
Values url.Values
}{{
NamespacePrefix: ".",
Values: url.Values{
"Phone[0].Number": []string{"1(111)111-1111"},
"Phone[1].Number": []string{"9(999)999-9999"},
"PhonePtr[0].Number": []string{"1(111)111-1111"},
"PhonePtr[1].Number": []string{"9(999)999-9999"},
"NonNilPtr.Number": []string{"9(999)999-9999"},
"Anonymous.Value": []string{"Anon"},
"TestMapKeys.MapIfaceKey[key]": []string{"3"},
"TestMapKeys.MapFloat32Key[0.0]": []string{"3.3"},
"TestMapKeys.MapFloat64Key[0.0]": []string{"3.3"},
"TestMapKeys.MapNestedInt[1][2]": []string{"3"},
"TestMapKeys.MapInt8[0]": []string{"3"},
"TestMapKeys.MapInt16[0]": []string{"3"},
"TestMapKeys.MapInt32[0]": []string{"3"},
"TestMapKeys.MapUint8[0]": []string{"3"},
"TestMapKeys.MapUint16[0]": []string{"3"},
"TestMapKeys.MapUint32[0]": []string{"3"},
},
}, {
NamespacePrefix: "[",
NamespaceSuffix: "]",
Values: url.Values{
"Phone[0][Number]": []string{"1(111)111-1111"},
"Phone[1][Number]": []string{"9(999)999-9999"},
"PhonePtr[0][Number]": []string{"1(111)111-1111"},
"PhonePtr[1][Number]": []string{"9(999)999-9999"},
"NonNilPtr[Number]": []string{"9(999)999-9999"},
"Anonymous[Value]": []string{"Anon"},
"TestMapKeys[MapIfaceKey][key]": []string{"3"},
"TestMapKeys[MapFloat32Key][0.0]": []string{"3.3"},
"TestMapKeys[MapFloat64Key][0.0]": []string{"3.3"},
"TestMapKeys[MapNestedInt][1][2]": []string{"3"},
"TestMapKeys[MapInt8][0]": []string{"3"},
"TestMapKeys[MapInt16][0]": []string{"3"},
"TestMapKeys[MapInt32][0]": []string{"3"},
"TestMapKeys[MapUint8][0]": []string{"3"},
"TestMapKeys[MapUint16][0]": []string{"3"},
"TestMapKeys[MapUint32][0]": []string{"3"},
},
}}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("Namespace_%s%s", tc.NamespacePrefix, tc.NamespaceSuffix), func(t *testing.T) {
decoder := NewDecoder()
decoder.SetNamespacePrefix(tc.NamespacePrefix)
decoder.SetNamespaceSuffix(tc.NamespaceSuffix)
values := url.Values{}
for key, vals := range defaultValues {
values[key] = vals
}
for key, vals := range tc.Values {
values[key] = vals
}
decoder.SetTagName("form")
decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return time.Parse("2006-01-02", vals[0])
}, time.Time{})
var test TestStruct
test.ExistingMap = map[string]string{"existingkey": "existingvalue"}
test.NonNilPtr = new(Phone)
test.IfaceNonNil = new(Phone)
test.IfaceInvalid = nil
test.TooSmallArray = []string{"0"}
test.TooSmallCapOKArray = make([]string, 0, 10)
test.TooSmallNumberedArray = []string{"0"}
test.TooSmallCapOKNumberedArray = make([]string, 0, 10)
test.BigEnoughNumberedArray = make([]string, 3, 10)
test.ExistingArray = []string{"arr1"}
test.ExistingArrayIndex = []string{"arr1"}
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.Name, "joeybloggs")
Equal(t, test.Ignore, "")
Equal(t, len(test.Phone), 2)
Equal(t, test.Phone[0].Number, "1(111)111-1111")
Equal(t, test.Phone[1].Number, "9(999)999-9999")
Equal(t, len(test.PhonePtr), 2)
Equal(t, test.PhonePtr[0].Number, "1(111)111-1111")
Equal(t, test.PhonePtr[1].Number, "9(999)999-9999")
Equal(t, test.NonNilPtr.Number, "9(999)999-9999")
Equal(t, test.Anonymous.Value, "Anon")
Equal(t, len(test.ExistingMap), 2)
Equal(t, test.ExistingMap["existingkey"], "existingvalue")
Equal(t, test.ExistingMap["key"], "value")
Equal(t, len(test.NilArray), 2)
Equal(t, test.NilArray[0], "1")
Equal(t, test.NilArray[1], "2")
Equal(t, len(test.TooSmallArray), 3)
Equal(t, test.TooSmallArray[0], "0")
Equal(t, test.TooSmallArray[1], "1")
Equal(t, test.TooSmallArray[2], "2")
Equal(t, len(test.ZeroLengthArray), 0)
Equal(t, len(test.TooSmallNumberedArray), 3)
Equal(t, test.TooSmallNumberedArray[0], "0")
Equal(t, test.TooSmallNumberedArray[1], "")
Equal(t, test.TooSmallNumberedArray[2], "2")
Equal(t, len(test.BigEnoughNumberedArray), 3)
Equal(t, cap(test.BigEnoughNumberedArray), 10)
Equal(t, test.BigEnoughNumberedArray[0], "")
Equal(t, test.BigEnoughNumberedArray[1], "")
Equal(t, test.BigEnoughNumberedArray[2], "1")
Equal(t, len(test.TooSmallCapOKArray), 2)
Equal(t, cap(test.TooSmallCapOKArray), 10)
Equal(t, test.TooSmallCapOKArray[0], "1")
Equal(t, test.TooSmallCapOKArray[1], "2")
Equal(t, len(test.TooSmallCapOKNumberedArray), 3)
Equal(t, cap(test.TooSmallCapOKNumberedArray), 10)
Equal(t, test.TooSmallCapOKNumberedArray[0], "")
Equal(t, test.TooSmallCapOKNumberedArray[1], "")
Equal(t, test.TooSmallCapOKNumberedArray[2], "2")
Equal(t, len(test.ExistingArray), 2)
Equal(t, test.ExistingArray[0], "arr1")
Equal(t, test.ExistingArray[1], "arr2")
Equal(t, len(test.ExistingArrayIndex), 2)
Equal(t, test.ExistingArrayIndex[0], "arr1")
Equal(t, test.ExistingArrayIndex[1], "arr2")
tm, _ := time.Parse("2006-01-02", "2016-01-02")
Equal(t, test.Time.Equal(tm), true)
Equal(t, test.TimePtr.Equal(tm), true)
NotEqual(t, test.TimeMapKey, nil)
Equal(t, len(test.TimeMapKey), 1)
_, ok := test.TimeMapKey[tm]
Equal(t, ok, true)
s := struct {
Value string
Ignore string `form:"-"`
unexposed string
}{}
errs = decoder.Decode(&s, defaultValues)
Equal(t, errs, nil)
Equal(t, s.Value, "")
Equal(t, s.Ignore, "")
Equal(t, s.unexposed, "")
Equal(t, test.TestMapKeys.MapIfaceKey["key"], "3")
Equal(t, test.TestMapKeys.MapFloat32Key[float32(0.0)], float32(3.3))
Equal(t, test.TestMapKeys.MapFloat64Key[float64(0.0)], float64(3.3))
Equal(t, test.TestMapKeys.MapInt8[int8(0)], int8(3))
Equal(t, test.TestMapKeys.MapInt16[int16(0)], int16(3))
Equal(t, test.TestMapKeys.MapInt32[int32(0)], int32(3))
Equal(t, test.TestMapKeys.MapUint8[uint8(0)], uint8(3))
Equal(t, test.TestMapKeys.MapUint16[uint16(0)], uint16(3))
Equal(t, test.TestMapKeys.MapUint32[uint32(0)], uint32(3))
Equal(t, len(test.TestMapKeys.MapNestedInt), 1)
Equal(t, len(test.TestMapKeys.MapNestedInt[1]), 1)
Equal(t, test.TestMapKeys.MapNestedInt[1][2], 3)
})
}
}
func TestDecoderNativeTime(t *testing.T) {
type TestError struct {
Time time.Time
TimeNoValue time.Time
TimePtr *time.Time
}
values := url.Values{
"Time": []string{"2006-01-02T15:04:05Z"},
"TimeNoValue": []string{""},
"TimePtr": []string{"2006-01-02T15:04:05Z"},
}
var test TestError
decoder := NewDecoder()
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
tm, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
Equal(t, test.Time.Equal(tm), true)
Equal(t, test.TimeNoValue.Equal(tm), false)
NotEqual(t, test.TimePtr, nil)
Equal(t, (*test.TimePtr).Equal(tm), true)
}
func TestDecoderErrors(t *testing.T) {
type TestError struct {
Bool bool `form:"bool"`
Int int
Int8 int8
Int16 int16
Int32 int32
Uint uint
Uint8 uint8
Uint16 uint16
Uint32 uint32
Float32 float32
Float64 float64
String string
Time time.Time
MapBadIntKey map[int]int
MapBadInt8Key map[int8]int8
MapBadInt16Key map[int16]int16
MapBadInt32Key map[int32]int32
MapBadUintKey map[uint]uint
MapBadUint8Key map[uint8]uint8
MapBadUint16Key map[uint16]uint16
MapBadUint32Key map[uint32]uint32
MapBadFloat32Key map[float32]float32
MapBadFloat64Key map[float64]float64
MapBadBoolKey map[bool]bool
MapBadKeyType map[complex64]int
BadArrayValue []int
BadMapKey map[time.Time]string
OverflowNilArray []int
OverFlowExistingArray []int
BadArrayIndex []int
}
values := url.Values{
"bool": []string{"uh-huh"},
"Int": []string{"bad"},
"Int8": []string{"bad"},
"Int16": []string{"bad"},
"Int32": []string{"bad"},
"Uint": []string{"bad"},
"Uint8": []string{"bad"},
"Uint16": []string{"bad"},
"Uint32": []string{"bad"},
"Float32": []string{"bad"},
"Float64": []string{"bad"},
"String": []string{"str bad return val"},
"Time": []string{"bad"},
"MapBadIntKey[key]": []string{"1"},
"MapBadInt8Key[key]": []string{"1"},
"MapBadInt16Key[key]": []string{"1"},
"MapBadInt32Key[key]": []string{"1"},
"MapBadUintKey[key]": []string{"1"},
"MapBadUint8Key[key]": []string{"1"},
"MapBadUint16Key[key]": []string{"1"},
"MapBadUint32Key[key]": []string{"1"},
"MapBadFloat32Key[key]": []string{"1.1"},
"MapBadFloat64Key[key]": []string{"1.1"},
"MapBadBoolKey[uh-huh]": []string{"true"},
"MapBadKeyType[1.4]": []string{"5"},
"BadArrayValue[0]": []string{"badintval"},
"BadMapKey[badtime]": []string{"badtime"},
"OverflowNilArray[999]": []string{"idx 1000"},
"OverFlowExistingArray[999]": []string{"idx 1000"},
"BadArrayIndex[bad index]": []string{"bad idx"},
}
testCases := []struct {
NamespacePrefix string
NamespaceSuffix string
}{{
NamespacePrefix: ".",
}, {
NamespacePrefix: "[",
NamespaceSuffix: "]",
}}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("Namespace_%s%s", tc.NamespacePrefix, tc.NamespaceSuffix), func(t *testing.T) {
decoder := NewDecoder()
decoder.SetNamespacePrefix(tc.NamespacePrefix)
decoder.SetNamespaceSuffix(tc.NamespaceSuffix)
decoder.SetMaxArraySize(4)
decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return nil, errors.New("Bad Type Conversion")
}, "")
test := TestError{
OverFlowExistingArray: make([]int, 2),
}
errs := decoder.Decode(&test, values)
NotEqual(t, errs, nil)
e := errs.Error()
NotEqual(t, e, "")
err := errs.(DecodeErrors)
Equal(t, len(err), 30)
k := err["bool"]
Equal(t, k.Error(), "Invalid Boolean Value 'uh-huh' Type 'bool' Namespace 'bool'")
k = err["Int"]
Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int' Namespace 'Int'")
k = err["Int8"]
Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int8' Namespace 'Int8'")
k = err["Int16"]
Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int16' Namespace 'Int16'")
k = err["Int32"]
Equal(t, k.Error(), "Invalid Integer Value 'bad' Type 'int32' Namespace 'Int32'")
k = err["Uint"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint' Namespace 'Uint'")
k = err["Uint8"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint8' Namespace 'Uint8'")
k = err["Uint16"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint16' Namespace 'Uint16'")
k = err["Uint32"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'bad' Type 'uint32' Namespace 'Uint32'")
k = err["Float32"]
Equal(t, k.Error(), "Invalid Float Value 'bad' Type 'float32' Namespace 'Float32'")
k = err["Float64"]
Equal(t, k.Error(), "Invalid Float Value 'bad' Type 'float64' Namespace 'Float64'")
k = err["String"]
Equal(t, k.Error(), "Bad Type Conversion")
k = err["Time"]
Equal(t, k.Error(), "parsing time \"bad\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"bad\" as \"2006\"")
k = err["MapBadIntKey"]
Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int' Namespace 'MapBadIntKey'")
k = err["MapBadInt8Key"]
Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int8' Namespace 'MapBadInt8Key'")
k = err["MapBadInt16Key"]
Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int16' Namespace 'MapBadInt16Key'")
k = err["MapBadInt32Key"]
Equal(t, k.Error(), "Invalid Integer Value 'key' Type 'int32' Namespace 'MapBadInt32Key'")
k = err["MapBadUintKey"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint' Namespace 'MapBadUintKey'")
k = err["MapBadUint8Key"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint8' Namespace 'MapBadUint8Key'")
k = err["MapBadUint16Key"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint16' Namespace 'MapBadUint16Key'")
k = err["MapBadUint32Key"]
Equal(t, k.Error(), "Invalid Unsigned Integer Value 'key' Type 'uint32' Namespace 'MapBadUint32Key'")
k = err["MapBadFloat32Key"]
Equal(t, k.Error(), "Invalid Float Value 'key' Type 'float32' Namespace 'MapBadFloat32Key'")
k = err["MapBadFloat64Key"]
Equal(t, k.Error(), "Invalid Float Value 'key' Type 'float64' Namespace 'MapBadFloat64Key'")
k = err["MapBadBoolKey"]
Equal(t, k.Error(), "Invalid Boolean Value 'uh-huh' Type 'bool' Namespace 'MapBadBoolKey'")
k = err["MapBadKeyType"]
Equal(t, k.Error(), "Unsupported Map Key '1.4', Type 'complex64' Namespace 'MapBadKeyType'")
k = err["BadArrayValue[0]"]
Equal(t, k.Error(), "Invalid Integer Value 'badintval' Type 'int' Namespace 'BadArrayValue[0]'")
k = err["OverflowNilArray"]
Equal(t, k.Error(), "Array size of '1000' is larger than the maximum currently set on the decoder of '4'. To increase this limit please see, SetMaxArraySize(size uint)")
k = err["OverFlowExistingArray"]
Equal(t, k.Error(), "Array size of '1000' is larger than the maximum currently set on the decoder of '4'. To increase this limit please see, SetMaxArraySize(size uint)")
k = err["BadArrayIndex"]
Equal(t, k.Error(), "invalid slice index 'bad index'")
type TestError2 struct {
BadMapKey map[time.Time]string
}
values2 := url.Values{
"BadMapKey[badtime]": []string{"badtime"},
}
var test2 TestError2
decoder2 := NewDecoder()
decoder2.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return time.Parse("2006-01-02", vals[0])
}, time.Time{})
errs = decoder2.Decode(&test2, values2)
NotEqual(t, errs, nil)
e = errs.Error()
NotEqual(t, e, "")
k = err["BadMapKey"]
Equal(t, k.Error(), "Unsupported Map Key 'badtime', Type 'time.Time' Namespace 'BadMapKey'")
})
}
}
func TestDecodeAllTypes(t *testing.T) {
values := url.Values{
"": []string{"3"},
}
decoder := NewDecoder()
var i int
errs := decoder.Decode(&i, values)
Equal(t, errs, nil)
Equal(t, i, 3)
var i8 int
errs = decoder.Decode(&i8, values)
Equal(t, errs, nil)
Equal(t, i8, 3)
var i16 int
errs = decoder.Decode(&i16, values)
Equal(t, errs, nil)
Equal(t, i16, 3)
var i32 int
errs = decoder.Decode(&i32, values)
Equal(t, errs, nil)
Equal(t, i32, 3)
var i64 int
errs = decoder.Decode(&i64, values)
Equal(t, errs, nil)
Equal(t, i64, 3)
var ui int
errs = decoder.Decode(&ui, values)
Equal(t, errs, nil)
Equal(t, ui, 3)
var ui8 int
errs = decoder.Decode(&ui8, values)
Equal(t, errs, nil)
Equal(t, ui8, 3)
var ui16 int
errs = decoder.Decode(&ui16, values)
Equal(t, errs, nil)
Equal(t, ui16, 3)
var ui32 int
errs = decoder.Decode(&ui32, values)
Equal(t, errs, nil)
Equal(t, ui32, 3)
var ui64 int
errs = decoder.Decode(&ui64, values)
Equal(t, errs, nil)
Equal(t, ui64, 3)
values = url.Values{
"": []string{"3.4"},
}
var f32 float32
errs = decoder.Decode(&f32, values)
Equal(t, errs, nil)
Equal(t, f32, float32(3.4))
var f64 float64
errs = decoder.Decode(&f64, values)
Equal(t, errs, nil)
Equal(t, f64, float64(3.4))
values = url.Values{
"": []string{"true"},
}
var b bool
errs = decoder.Decode(&b, values)
Equal(t, errs, nil)
Equal(t, b, true)
tm, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
values = url.Values{
"": []string{"2006-01-02T15:04:05Z"},
}
var dt time.Time
errs = decoder.Decode(&dt, values)
Equal(t, errs, nil)
Equal(t, dt, tm)
values = url.Values{
"": []string{"arr1", "arr2"},
}
// basic array
var arr []string
errs = decoder.Decode(&arr, values)
Equal(t, errs, nil)
Equal(t, len(arr), 2)
Equal(t, arr[0], "arr1")
Equal(t, arr[1], "arr2")
// pre-populated array
// fmt.Println("Decoding...")
errs = decoder.Decode(&arr, values)
Equal(t, errs, nil)
Equal(t, len(arr), 4)
Equal(t, arr[0], "arr1")
Equal(t, arr[1], "arr2")
Equal(t, arr[2], "arr1")
Equal(t, arr[3], "arr2")
// basic array Ptr
var arrPtr []*string
errs = decoder.Decode(&arrPtr, values)
Equal(t, errs, nil)
Equal(t, len(arrPtr), 2)
Equal(t, *arrPtr[0], "arr1")
Equal(t, *arrPtr[1], "arr2")
// pre-populated array Ptr
// fmt.Println("Decoding...")
errs = decoder.Decode(&arrPtr, values)
Equal(t, errs, nil)
Equal(t, len(arr), 4)
Equal(t, *arrPtr[0], "arr1")
Equal(t, *arrPtr[1], "arr2")
Equal(t, *arrPtr[2], "arr1")
Equal(t, *arrPtr[3], "arr2")
// indexed array
values = url.Values{
"[0]": []string{"newVal1"},
"[1]": []string{"newVal2"},
}
errs = decoder.Decode(&arr, values)
Equal(t, errs, nil)
Equal(t, len(arr), 4)
Equal(t, arr[0], "newVal1")
Equal(t, arr[1], "newVal2")
Equal(t, arr[2], "arr1")
Equal(t, arr[3], "arr2")
values = url.Values{
"[key1]": []string{"val1"},
"[key2]": []string{"val2"},
}
// basic map
var m map[string]string
errs = decoder.Decode(&m, values)
Equal(t, errs, nil)
Equal(t, len(m), 2)
Equal(t, m["key1"], "val1")
Equal(t, m["key2"], "val2")
// existing map
errs = decoder.Decode(&m, values)
Equal(t, errs, nil)
Equal(t, len(m), 2)
Equal(t, m["key1"], "val1")
Equal(t, m["key2"], "val2")
// basic map, adding more keys
values = url.Values{
"[key3]": []string{"val3"},
}
errs = decoder.Decode(&m, values)
Equal(t, errs, nil)
Equal(t, len(m), 3)
Equal(t, m["key3"], "val3")
// array of struct
type Phone struct {
Number string
Label string
}
values = url.Values{
"[0].Number": []string{"999"},
"[1].Label": []string{"label2"},
"[1].Number": []string{"111"},
"[0].Label": []string{"label1"},
}
var phones []Phone
errs = decoder.Decode(&phones, values)
Equal(t, errs, nil)
Equal(t, len(phones), 2)
Equal(t, phones[0].Number, "999")
Equal(t, phones[0].Label, "label1")
Equal(t, phones[1].Number, "111")
Equal(t, phones[1].Label, "label2")
}
func TestDecoderPanicsAndBadValues(t *testing.T) {
type Phone struct {
Number string
}
type TestError struct {
Phone []Phone
Phone2 []Phone
Phone3 []Phone
}
values := url.Values{
"Phone[0.Number": []string{"1(111)111-1111"},
}
var test TestError
decoder := NewDecoder()
PanicMatches(t, func() { _ = decoder.Decode(&test, values) }, "Invalid formatting for key 'Phone[0.Number' missing ']' bracket")
i := 1
err := decoder.Decode(i, values)
NotEqual(t, err, nil)
_, ok := err.(*InvalidDecoderError)
Equal(t, ok, true)
Equal(t, err.Error(), "form: Decode(non-pointer int)")
err = decoder.Decode(nil, values)
NotEqual(t, err, nil)
_, ok = err.(*InvalidDecoderError)
Equal(t, ok, true)
Equal(t, err.Error(), "form: Decode(nil)")
var ts *TestError
err = decoder.Decode(ts, values)
NotEqual(t, err, nil)
_, ok = err.(*InvalidDecoderError)
Equal(t, ok, true)
Equal(t, err.Error(), "form: Decode(nil *form.TestError)")
values = url.Values{
"Phone0].Number": []string{"1(111)111-1111"},
}
PanicMatches(t, func() { _ = decoder.Decode(&test, values) }, "Invalid formatting for key 'Phone0].Number' missing '[' bracket")
values = url.Values{
"Phone[[0.Number": []string{"1(111)111-1111"},
}
PanicMatches(t, func() { _ = decoder.Decode(&test, values) }, "Invalid formatting for key 'Phone[[0.Number' missing ']' bracket")
values = url.Values{
"Phone0]].Number": []string{"1(111)111-1111"},
}
PanicMatches(t, func() { _ = decoder.Decode(&test, values) }, "Invalid formatting for key 'Phone0]].Number' missing '[' bracket")
}
func TestDecoderMapKeys(t *testing.T) {
type TestMapKeys struct {
MapIfaceKey map[interface{}]string
MapFloat32Key map[float32]float32
MapFloat64Key map[float64]float64
MapNestedInt map[int]map[int]int
MapInt8 map[int8]int8
MapInt16 map[int16]int16
MapInt32 map[int32]int32
MapUint8 map[uint8]uint8
MapUint16 map[uint16]uint16
MapUint32 map[uint32]uint32
}
values := url.Values{
"MapIfaceKey[key]": []string{"3"},
"MapFloat32Key[0.0]": []string{"3.3"},
"MapFloat64Key[0.0]": []string{"3.3"},
"MapNestedInt[1][2]": []string{"3"},
"MapInt8[0]": []string{"3"},
"MapInt16[0]": []string{"3"},
"MapInt32[0]": []string{"3"},
"MapUint8[0]": []string{"3"},
"MapUint16[0]": []string{"3"},
"MapUint32[0]": []string{"3"},
}
var test TestMapKeys
testCases := []struct {
NamespacePrefix string
NamespaceSuffix string
}{
{
NamespacePrefix: ".",
},
{
NamespacePrefix: "[",
NamespaceSuffix: "]",
},
}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("Namespace_%s%s", tc.NamespacePrefix, tc.NamespaceSuffix), func(t *testing.T) {
decoder := NewDecoder()
decoder.SetNamespacePrefix(tc.NamespacePrefix)
decoder.SetNamespaceSuffix(tc.NamespaceSuffix)
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.MapIfaceKey["key"], "3")
Equal(t, test.MapFloat32Key[float32(0.0)], float32(3.3))
Equal(t, test.MapFloat64Key[float64(0.0)], float64(3.3))
Equal(t, test.MapInt8[int8(0)], int8(3))
Equal(t, test.MapInt16[int16(0)], int16(3))
Equal(t, test.MapInt32[int32(0)], int32(3))
Equal(t, test.MapUint8[uint8(0)], uint8(3))
Equal(t, test.MapUint16[uint16(0)], uint16(3))
Equal(t, test.MapUint32[uint32(0)], uint32(3))
Equal(t, len(test.MapNestedInt), 1)
Equal(t, len(test.MapNestedInt[1]), 1)
Equal(t, test.MapNestedInt[1][2], 3)
})
}
}
func TestDecoderStructRecursion(t *testing.T) {
type Nested struct {
Value string
Nested *Nested
}
type TestRecursive struct {
Nested Nested
NestedPtr *Nested
NestedTwo Nested
}
defaultValues := url.Values{
"Value": []string{"value"},
}
testCases := []struct {
Values url.Values
NamespacePrefix string
NamespaceSuffix string
}{{
NamespacePrefix: ".",
Values: url.Values{
"Nested.Value": []string{"value"},
"NestedPtr.Value": []string{"value"},
"NestedTwo.Nested.Value": []string{"value"},
},
}, {
NamespacePrefix: "[",
NamespaceSuffix: "]",
Values: url.Values{
"Nested[Value]": []string{"value"},
"NestedPtr[Value]": []string{"value"},
"NestedTwo[Nested][Value]": []string{"value"},
},
}}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("Namespace_%s%s", tc.NamespacePrefix, tc.NamespaceSuffix), func(t *testing.T) {
values := url.Values{}
for key, vals := range defaultValues {
values[key] = vals
}
for key, vals := range tc.Values {
values[key] = vals
}
decoder := NewDecoder()
decoder.SetNamespacePrefix(tc.NamespacePrefix)
decoder.SetNamespaceSuffix(tc.NamespaceSuffix)
var test TestRecursive
errs := decoder.Decode(&test, values)
Equal(t, errs, nil)
Equal(t, test.Nested.Value, "value")
Equal(t, test.NestedPtr.Value, "value")
Equal(t, test.Nested.Nested, nil)
Equal(t, test.NestedTwo.Nested.Value, "value")
})
}
}
func TestDecoderFormDecode(t *testing.T) {
type Struct2 struct {
Foo string
Bar string
}
type Struct2Wrapper struct {
InnerSlice []Struct2
}
sliceValues := map[string][]string{
"InnerSlice[0].Foo": {"foo-is-set"},
}
singleValues := map[string][]string{
"Foo": {"foo-is-set"},
}
fd := NewDecoder()
dst := Struct2Wrapper{}
err := fd.Decode(&dst, sliceValues)
Equal(t, err, nil)
NotEqual(t, dst.InnerSlice, nil)
Equal(t, dst.InnerSlice[0].Foo, "foo-is-set")
dst2 := Struct2{}
err = fd.Decode(&dst2, singleValues)
Equal(t, err, nil)
Equal(t, dst2.Foo, "foo-is-set")
}
func TestDecoderArrayKeysSort(t *testing.T) {
type Struct struct {
Array []int
}
values := map[string][]string{
"Array[2]": {"2"},
"Array[10]": {"10"},
}
var test Struct
d := NewDecoder()
err := d.Decode(&test, values)
Equal(t, err, nil)
Equal(t, len(test.Array), 11)
Equal(t, test.Array[2], int(2))
Equal(t, test.Array[10], int(10))
}
func TestDecoderIncreasingKeys(t *testing.T) {
type Struct struct {
Array []int
}
values := map[string][]string{
"Array[2]": {"2"},
}
var test Struct
d := NewDecoder()
err := d.Decode(&test, values)
Equal(t, err, nil)
Equal(t, len(test.Array), 3)
Equal(t, test.Array[2], int(2))
values["Array[10]"] = []string{"10"}
var test2 Struct
err = d.Decode(&test2, values)
Equal(t, err, nil)
Equal(t, len(test2.Array), 11)
Equal(t, test2.Array[2], int(2))
Equal(t, test2.Array[10], int(10))
}
func TestDecoderInterface(t *testing.T) {
var iface interface{}
d := NewDecoder()
values := map[string][]string{
"": {"2"},
}
var i int
iface = &i
err := d.Decode(iface, values)
Equal(t, err, nil)
Equal(t, i, 2)
iface = i
err = d.Decode(iface, values)
NotEqual(t, err, nil)
_, ok := err.(*InvalidDecoderError)
Equal(t, ok, true)
Equal(t, err.Error(), "form: Decode(non-pointer int)")
values = map[string][]string{
"Value": {"testVal"},
}
type test struct {
Value string
}
var tst test
iface = &tst
err = d.Decode(iface, values)
Equal(t, err, nil)
Equal(t, tst.Value, "testVal")
iface = tst
err = d.Decode(iface, values)
NotEqual(t, err, nil)
_, ok = err.(*InvalidDecoderError)
Equal(t, ok, true)
Equal(t, err.Error(), "form: Decode(non-pointer form.test)")
}
func TestDecoderPointerToPointer(t *testing.T) {
values := map[string][]string{
"Value": {"testVal"},
}
type Test struct {
Value string
}
var tst *Test
d := NewDecoder()
err := d.Decode(&tst, values)
Equal(t, err, nil)
Equal(t, tst.Value, "testVal")
}
func TestDecoderExplicit(t *testing.T) {
type Test struct {
Name string `form:"Name"`
Age int
}
values := map[string][]string{
"Name": {"Joeybloggs"},
"Age": {"3"},
}
var test Test
d := NewDecoder()
d.SetMode(ModeExplicit)
err := d.Decode(&test, values)
Equal(t, err, nil)
Equal(t, test.Name, "Joeybloggs")
Equal(t, test.Age, 0)
}
func TestDecoderStructWithJSONTag(t *testing.T) {
type Test struct {
Name string `json:"name,omitempty"`
Age int `json:",omitempty"`
}
values := map[string][]string{
"name": {"Joeybloggs"},
"Age": {"3"},
}
var test Test
d := NewDecoder()
d.SetTagName("json")
err := d.Decode(&test, values)
Equal(t, err, nil)
Equal(t, test.Name, "Joeybloggs")
Equal(t, test.Age, int(3))
}
func TestDecoderRegisterTagNameFunc(t *testing.T) {
type Test struct {
Value string `json:"val,omitempty"`
Ignore string `json:"-"`
}
values := url.Values{
"val": []string{"joeybloggs"},
"Ignore": []string{"ignore"},
}
var test Test
decoder := NewDecoder()
decoder.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := fld.Tag.Get("json")
if commaIndex := strings.Index(name, ","); commaIndex != -1 {
name = name[:commaIndex]
}
return name
})
err := decoder.Decode(&test, values)
Equal(t, err, nil)
Equal(t, test.Value, "joeybloggs")
Equal(t, test.Ignore, "")
}
func TestDecoderEmbedModes(t *testing.T) {
type A struct {
Field string
}
type B struct {
A
Field string
}
var b B
decoder := NewDecoder()
values := url.Values{
"Field": []string{"Value"},
}
err := decoder.Decode(&b, values)
Equal(t, err, nil)
Equal(t, b.Field, "Value")
Equal(t, b.A.Field, "Value")
values = url.Values{
"Field": []string{"B Val"},
"A.Field": []string{"A Val"},
}
err = decoder.Decode(&b, values)
Equal(t, err, nil)
Equal(t, b.Field, "B Val")
Equal(t, b.A.Field, "A Val")
}
func TestInterfaceDecoding(t *testing.T) {
type Test struct {
Iface interface{}
}
var b Test
values := url.Values{
"Iface": []string{"1"},
}
decoder := NewDecoder()
err := decoder.Decode(&b, values)
Equal(t, err, nil)
Equal(t, b.Iface, "1")
}
func TestDecodeArrayBug(t *testing.T) {
var data struct {
A [2]string
B [2]string
C [2]string
D [3]string
E [3]string
F [3]string
G [3]string
}
decoder := NewDecoder()
err := decoder.Decode(&data, url.Values{
// Mixed types
"A": {"10"},
"A[1]": {"20"},
// overflow
"B": {"10", "20", "30"},
"B[1]": {"31", "10", "20"},
"B[2]": {"40"},
// invalid array index
"C[q]": {""},
// index and mix tests
"D": {"10"},
"E": {"10", "20"},
"F": {"10", "", "20"},
"G": {"10"},
"G[2]": {"20"},
})
NotEqual(t, err, nil)
Equal(t, err.Error(), "Field Namespace:C ERROR:invalid array index 'q'")
Equal(t, data.A[0], "10")
Equal(t, data.A[1], "20")
Equal(t, data.B[0], "10")
Equal(t, data.B[1], "31")
Equal(t, data.C[0], "")
Equal(t, data.C[1], "")
Equal(t, data.D[0], "10")
Equal(t, data.D[1], "")
Equal(t, data.D[2], "")
Equal(t, data.E[0], "10")
Equal(t, data.E[1], "20")
Equal(t, data.E[2], "")
Equal(t, data.F[0], "10")
Equal(t, data.F[1], "")
Equal(t, data.F[2], "20")
Equal(t, data.G[0], "10")
Equal(t, data.G[1], "")
Equal(t, data.G[2], "20")
}
func TestDecoder_RegisterCustomTypeFuncOnSlice(t *testing.T) {
type customString string
type TestStruct struct {
Slice []customString `form:"slice"`
}
d := NewDecoder()
d.RegisterCustomTypeFunc(func(vals []string) (i interface{}, e error) {
custom := make([]customString, 0, len(vals))
for i := 0; i < len(vals); i++ {
custom = append(custom, customString("custom"+vals[i]))
}
return custom, nil
}, []customString{})
var v TestStruct
err := d.Decode(&v, url.Values{"slice": []string{"v1", "v2"}})
Equal(t, err, nil)
Equal(t, v.Slice, []customString{"customv1", "customv2"})
}
func TestDecoder_RegisterCustomTypeFunc(t *testing.T) {
type customString string
type TestStruct struct {
Slice []customString `form:"slice"`
}
d := NewDecoder()
d.RegisterCustomTypeFunc(func(vals []string) (i interface{}, e error) {
return customString("custom" + vals[0]), nil
}, customString(""))
var v TestStruct
err := d.Decode(&v, url.Values{"slice": []string{"v1", "v2"}})
Equal(t, err, nil)
Equal(t, v.Slice, []customString{"customv1", "customv2"})
}
func TestDecoder_CustomTypeFuncPointerNilValue(t *testing.T) {
type User struct {
Name string
}
type TestStruct struct {
U *User `form:"u"`
}
d := NewDecoder()
d.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
if vals[0] == "" {
// Signals "leave the field at its zero value" for an absent input,
// this must not panic even though the field itself is a pointer.
return nil, nil
}
return &User{Name: vals[0]}, nil
}, &User{})
var empty TestStruct
err := d.Decode(&empty, url.Values{"u": {""}})
Equal(t, err, nil)
Equal(t, empty.U, nil)
var populated TestStruct
err = d.Decode(&populated, url.Values{"u": {"bob"}})
Equal(t, err, nil)
NotEqual(t, populated.U, nil)
Equal(t, populated.U.Name, "bob")
}
func TestDecoder_EmptyArrayString(t *testing.T) {
type T1 struct {
F1 string `form:"F1"`
}
in := url.Values{
"F1": []string{},
}
v := new(T1)
d := NewDecoder()
err := d.Decode(v, in)
Equal(t, err, nil)
}
func TestDecoder_EmptyArrayBool(t *testing.T) {
type T1 struct {
F1 bool `form:"F1"`
}
in := url.Values{
"F1": []string{},
}
v := new(T1)
d := NewDecoder()
err := d.Decode(v, in)
Equal(t, err, nil)
}
func TestDecoder_InvalidSliceIndex(t *testing.T) {
type PostsRequest struct {
PostIds []string
}
in := url.Values{
"PostIds[]": []string{"1", "2"},
}
v := new(PostsRequest)
d := NewDecoder()
err := d.Decode(v, in)
NotEqual(t, err, nil)
Equal(t, err.Error(), "Field Namespace:PostIds ERROR:invalid slice index ''")
// No error with proper name
type PostsRequest2 struct {
PostIds []string `form:"PostIds[]"`
}
v2 := new(PostsRequest2)
err = d.Decode(v2, in)
Equal(t, err, nil)
Equal(t, len(v2.PostIds), 2)
Equal(t, v2.PostIds[0], "1")
Equal(t, v2.PostIds[1], "2")
}
// TestNestedArrayPerformance verifies decoding performance for nested structures.
// - Uses more lenient thresholds when race detector is enabled, since it can slow down execution.
func TestNestedArrayPerformance(t *testing.T) {
type NestedBar struct {
Bazs []string `form:"bazs"`
Lookup map[string]string `form:"lookup"`
}
type NestedFoo struct {
Bars []*NestedBar `form:"bars"`
}
type FormRequest struct {
Foos []*NestedFoo `form:"foos"`
}
decoder := NewDecoder()
var thresholdTests []struct {
numValues int
maxTime time.Duration
}
if raceEnabled {
thresholdTests = []struct {
numValues int
maxTime time.Duration
}{
{10, 50 * time.Millisecond},
{50, 500 * time.Millisecond},
{200, 5 * time.Second},
}
} else {
thresholdTests = []struct {
numValues int
maxTime time.Duration
}{
{10, 10 * time.Millisecond},
{50, 50 * time.Millisecond},
{200, 500 * time.Millisecond},
}
}
for _, tt := range thresholdTests {
urlValues := make(url.Values)
for i := 0; i < tt.numValues; i++ {
urlValues.Add(fmt.Sprintf("foos[0].bars[%d].bazs", i), fmt.Sprintf("value%d", i))
urlValues.Add(fmt.Sprintf("foos[0].bars[%d].lookup[A]", i), fmt.Sprintf("lookupA%d", i))
}
var req FormRequest
start := time.Now()
err := decoder.Decode(&req, urlValues)
elapsed := time.Since(start)
if err != nil {
t.Errorf("Decode error for %d values: %v", tt.numValues, err)
}
if len(req.Foos) != 1 {
t.Errorf("Expected 1 Foo, got %d", len(req.Foos))
}
if len(req.Foos[0].Bars) != tt.numValues {
t.Errorf("Expected %d Bars, got %d", tt.numValues, len(req.Foos[0].Bars))
}
if elapsed > tt.maxTime {
t.Errorf("[race=%t] Decoding %d values took %v, expected less than %v",
raceEnabled, tt.numValues, elapsed, tt.maxTime)
}
}
}
func BenchmarkNestedArrayDecode100(b *testing.B) {
type NestedBar struct {
Bazs []string `form:"bazs"`
Lookup map[string]string `form:"lookup"`
}
type NestedFoo struct {
Bars []*NestedBar `form:"bars"`
}
type FormRequest struct {
Foos []*NestedFoo `form:"foos"`
}
decoder := NewDecoder()
urlValues := make(url.Values)
for i := 0; i < 100; i++ {
urlValues.Add(fmt.Sprintf("foos[0].bars[%d].bazs", i), fmt.Sprintf("value%d", i))
urlValues.Add(fmt.Sprintf("foos[0].bars[%d].lookup[A]", i), fmt.Sprintf("lookupA%d", i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var req FormRequest
if err := decoder.Decode(&req, urlValues); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkNestedArrayDecode1000(b *testing.B) {
type NestedBar struct {
Bazs []string `form:"bazs"`
Lookup map[string]string `form:"lookup"`
}
type NestedFoo struct {
Bars []*NestedBar `form:"bars"`
}
type FormRequest struct {
Foos []*NestedFoo `form:"foos"`
}
decoder := NewDecoder()
urlValues := make(url.Values)
for i := 0; i < 1000; i++ {
urlValues.Add(fmt.Sprintf("foos[0].bars[%d].bazs", i), fmt.Sprintf("value%d", i))
urlValues.Add(fmt.Sprintf("foos[0].bars[%d].lookup[A]", i), fmt.Sprintf("lookupA%d", i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var req FormRequest
if err := decoder.Decode(&req, urlValues); err != nil {
b.Fatal(err)
}
}
}