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) } } }