diff --git a/README.md b/README.md index 2e76aee..a24a90b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It has the following features: Common Questions -- Does it support encoding.TextUnmarshaler? No because TextUnmarshaler only accepts []byte but posted values can have multiple values, so is not suitable. +- Does it support encoding.TextUnmarshaler? No, instead we have `form.Unmarshaler` because TextUnmarshaler only accepts []byte but posted values can have multiple values, so is not suitable. - Mixing `array/slice` with `array[idx]/slice[idx]`, in which order are they parsed? `array/slice` then `array[idx]/slice[idx]` Supported Types ( out of the box ) @@ -230,6 +230,33 @@ encoder.RegisterCustomTypeFunc(func(x interface{}) ([]string, error) { }, time.Time{}) ``` +Implementing Marshaler and Unmarshaler +-------------- +Marshaler +```go +type CustomStruct struct { + A string + B string +} + +func (c CustomStruct) MarshalForm() ([]string, error) { + return []string{ c.A, c.B }, nil +} +``` + +Unmarshaler +```go +type CustomStruct struct { + A string + B string +} + +func (c *CustomStruct) UnmarshalForm(ss []string) error { + c.A = ss[0] + c.B = ss[1] +} +``` + Ignoring Fields -------------- you can tell form to ignore fields using `-` in the tag diff --git a/decoder.go b/decoder.go index e212422..72c470e 100644 --- a/decoder.go +++ b/decoder.go @@ -9,6 +9,8 @@ import ( "time" ) +var unmarhsalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + const ( errArraySize = "Array size of '%d' is larger than the maximum currently set on the decoder of '%d'. To increase this limit please see, SetMaxArraySize(size uint)" errMissingStartBracket = "Invalid formatting for key '%s' missing '[' bracket" @@ -63,7 +65,6 @@ func (d *decoder) parseMapData() { } for i = 0; i < len(k); i++ { - switch k[i] { case '[': idx = i @@ -117,7 +118,6 @@ func (d *decoder) parseMapData() { if ke.ivalue > rd.sliceLen { rd.sliceLen = ke.ivalue - } } @@ -140,7 +140,6 @@ func (d *decoder) parseMapData() { } func (d *decoder) traverseStruct(v reflect.Value, typ reflect.Type, namespace []byte) (set bool) { - l := len(namespace) first := l == 0 @@ -177,14 +176,12 @@ func (d *decoder) traverseStruct(v reflect.Value, typ reflect.Type, namespace [] } func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx int) (set bool) { - var err error v, kind := ExtractType(current) arr, ok := d.values[string(namespace)] if d.d.customTypeFuncs != nil { - if ok { if cf, ok := d.d.customTypeFuncs[v.Type()]; ok { val, err := cf(arr[idx:]) @@ -199,6 +196,14 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } } } + + if set, err = d.unmarshal(v, idx, arr); err != nil { + d.setError(namespace, err) + return + } else if set { + return + } + switch kind { case reflect.Interface: if !ok || idx == len(arr) { @@ -602,7 +607,6 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) (err error) { - v, kind := ExtractType(current) if d.d.customTypeFuncs != nil { @@ -750,3 +754,38 @@ func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) return } + +func (d *decoder) unmarshal(v reflect.Value, idx int, arr []string) (bool, error) { + t := v.Type() + if t.Kind() != reflect.Ptr && v.CanAddr() { + v = v.Addr() + t = v.Type() + } + if v.Type().NumMethod() == 0 || !v.CanInterface() { + return false, nil + } + + if !t.Implements(unmarhsalerType) { + return false, nil + } + + if t.Kind() == reflect.Ptr && v.CanAddr() { + return d.unmarshalAddr(v, idx, arr) + } + + um := v.Interface().(Unmarshaler) + if err := um.UnmarshalForm(arr[idx:]); err != nil { + return false, err + } + return true, nil +} + +func (d *decoder) unmarshalAddr(v reflect.Value, idx int, arr []string) (bool, error) { + nv := reflect.New(v.Type().Elem()) + um := nv.Interface().(Unmarshaler) + if err := um.UnmarshalForm(arr[idx:]); err != nil { + return false, err + } + v.Set(nv) + return true, nil +} diff --git a/decoder_test.go b/decoder_test.go index 99897ed..2ace30d 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -31,7 +31,6 @@ import ( // go test -memprofile mem.out func TestDecoderInt(t *testing.T) { - type TestInt struct { Int int Int8 int8 @@ -150,7 +149,6 @@ func TestDecoderInt(t *testing.T) { } func TestDecoderUint(t *testing.T) { - type TestUint struct { Uint uint Uint8 uint8 @@ -269,7 +267,6 @@ func TestDecoderUint(t *testing.T) { } func TestDecoderString(t *testing.T) { - type TestString struct { String string StringPtr *string @@ -353,7 +350,6 @@ func TestDecoderString(t *testing.T) { } func TestDecoderFloat(t *testing.T) { - type TestFloat struct { Float32 float32 Float32Ptr *float32 @@ -445,7 +441,6 @@ func TestDecoderFloat(t *testing.T) { } func TestDecoderBool(t *testing.T) { - type TestBool struct { Bool bool BoolPtr *bool @@ -588,7 +583,6 @@ func TestDecoderEqualStructMapValue(t *testing.T) { } func TestDecoderStruct(t *testing.T) { - type Phone struct { Number string } @@ -829,7 +823,6 @@ func TestDecoderStruct(t *testing.T) { } func TestDecoderNativeTime(t *testing.T) { - type TestError struct { Time time.Time TimeNoValue time.Time @@ -858,7 +851,6 @@ func TestDecoderNativeTime(t *testing.T) { } func TestDecoderErrors(t *testing.T) { - type TestError struct { Bool bool `form:"bool"` Int int @@ -1074,7 +1066,6 @@ func TestDecoderErrors(t *testing.T) { } func TestDecodeAllTypes(t *testing.T) { - values := url.Values{ "": []string{"3"}, } @@ -1297,7 +1288,6 @@ func TestDecodeAllTypes(t *testing.T) { } func TestDecoderPanicsAndBadValues(t *testing.T) { - type Phone struct { Number string } @@ -1362,7 +1352,6 @@ func TestDecoderPanicsAndBadValues(t *testing.T) { } func TestDecoderMapKeys(t *testing.T) { - type TestMapKeys struct { MapIfaceKey map[interface{}]string MapFloat32Key map[float32]float32 @@ -1434,7 +1423,6 @@ func TestDecoderMapKeys(t *testing.T) { } func TestDecoderStructRecursion(t *testing.T) { - type Nested struct { Value string Nested *Nested @@ -1498,11 +1486,9 @@ func TestDecoderStructRecursion(t *testing.T) { Equal(t, test.NestedTwo.Nested.Value, "value") }) } - } func TestDecoderFormDecode(t *testing.T) { - type Struct2 struct { Foo string Bar string @@ -1535,13 +1521,11 @@ func TestDecoderFormDecode(t *testing.T) { } func TestDecoderArrayKeysSort(t *testing.T) { - type Struct struct { Array []int } values := map[string][]string{ - "Array[2]": {"2"}, "Array[10]": {"10"}, } @@ -1559,7 +1543,6 @@ func TestDecoderArrayKeysSort(t *testing.T) { } func TestDecoderIncreasingKeys(t *testing.T) { - type Struct struct { Array []int } @@ -1591,7 +1574,6 @@ func TestDecoderIncreasingKeys(t *testing.T) { } func TestDecoderInterface(t *testing.T) { - var iface interface{} d := NewDecoder() @@ -1644,7 +1626,6 @@ func TestDecoderInterface(t *testing.T) { } func TestDecoderPointerToPointer(t *testing.T) { - values := map[string][]string{ "Value": {"testVal"}, } @@ -1662,7 +1643,6 @@ func TestDecoderPointerToPointer(t *testing.T) { } func TestDecoderExplicit(t *testing.T) { - type Test struct { Name string `form:"Name"` Age int @@ -1707,7 +1687,6 @@ func TestDecoderStructWithJSONTag(t *testing.T) { } func TestDecoderRegisterTagNameFunc(t *testing.T) { - type Test struct { Value string `json:"val,omitempty"` Ignore string `json:"-"` @@ -1738,7 +1717,6 @@ func TestDecoderRegisterTagNameFunc(t *testing.T) { } func TestDecoderEmbedModes(t *testing.T) { - type A struct { Field string } @@ -1773,7 +1751,6 @@ func TestDecoderEmbedModes(t *testing.T) { } func TestInterfaceDecoding(t *testing.T) { - type Test struct { Iface interface{} } @@ -1936,3 +1913,109 @@ func TestDecoder_InvalidSliceIndex(t *testing.T) { Equal(t, v2.PostIds[0], "1") Equal(t, v2.PostIds[1], "2") } + +type unmarshaler struct { + fname string + sname string +} + +func (u *unmarshaler) UnmarshalForm(ss []string) error { + if len(ss) != 2 { + return errors.New("invalid value") + } + u.fname = ss[0] + u.sname = ss[1] + return nil +} + +func TestDecoder_UnmarshalForm(t *testing.T) { + type T1 struct { + Ptr *unmarshaler + NilPtr *unmarshaler + Struct unmarshaler + Slice []unmarshaler + SlicePtr []*unmarshaler + Map map[string]unmarshaler + MapPtr map[string]*unmarshaler + } + + in := url.Values{ + "Ptr": []string{"ptrfname", "ptrsname"}, + "NilPtr": []string{"nilptrfname", "nilptrsname"}, + "Struct": []string{"structfname", "structsname"}, + "Slice[0]": []string{"slice0fname", "slice0sname"}, + "Slice[1]": []string{"slice1fname", "slice1sname"}, + "SlicePtr[0]": []string{"sliceptr0fname", "sliceptr0sname"}, + "SlicePtr[1]": []string{"sliceptr1fname", "sliceptr1sname"}, + "Map[key1]": []string{"mapk1fname", "mapk1sname"}, + "Map[key2]": []string{"mapk2fname", "mapk2sname"}, + "MapPtr[key1]": []string{"mapptrk1fname", "mapptrk1sname"}, + "MapPtr[key2]": []string{"mapptrk2fname", "mapptrk2sname"}, + } + + v := new(T1) + v.Ptr = &unmarshaler{} + err := NewDecoder().Decode(v, in) + Equal(t, err, nil) + NotEqual(t, v.NilPtr, nil) + Equal(t, v.Ptr.fname, "ptrfname") + Equal(t, v.Ptr.sname, "ptrsname") + Equal(t, v.NilPtr.fname, "nilptrfname") + Equal(t, v.NilPtr.sname, "nilptrsname") + Equal(t, v.Struct.fname, "structfname") + Equal(t, v.Struct.sname, "structsname") + + Equal(t, len(v.Slice), 2) + Equal(t, v.Slice[0].fname, "slice0fname") + Equal(t, v.Slice[1].fname, "slice1fname") + + Equal(t, len(v.SlicePtr), 2) + Equal(t, v.SlicePtr[0].fname, "sliceptr0fname") + Equal(t, v.SlicePtr[1].fname, "sliceptr1fname") + + Equal(t, len(v.Map), 2) + Equal(t, v.Map["key1"].fname, "mapk1fname") + Equal(t, v.Map["key1"].sname, "mapk1sname") + Equal(t, v.Map["key2"].fname, "mapk2fname") + Equal(t, v.Map["key2"].sname, "mapk2sname") + + Equal(t, len(v.MapPtr), 2) + Equal(t, v.MapPtr["key1"].fname, "mapptrk1fname") + Equal(t, v.MapPtr["key1"].sname, "mapptrk1sname") + Equal(t, v.MapPtr["key2"].fname, "mapptrk2fname") + Equal(t, v.MapPtr["key2"].sname, "mapptrk2sname") +} + +func TestDecoder_UnmarshalForm_Error(t *testing.T) { + in := url.Values{ + "Ptr": []string{"John"}, + "NilPtr": []string{"John"}, + "Struct": []string{"John"}, + } + d := NewDecoder() + + type t1 struct { + Ptr *unmarshaler + } + v1 := new(t1) + err := d.Decode(v1, in) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:Ptr ERROR:invalid value") + + type t2 struct { + NilPtr *unmarshaler + } + + v2 := new(t2) + err = d.Decode(v2, in) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:NilPtr ERROR:invalid value") + + type t3 struct { + Struct unmarshaler + } + v3 := new(t3) + err = d.Decode(v3, in) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:Struct ERROR:invalid value") +} diff --git a/encoder.go b/encoder.go index eea0bd9..2ab8468 100644 --- a/encoder.go +++ b/encoder.go @@ -8,6 +8,8 @@ import ( "time" ) +var marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + type encoder struct { e *Encoder errs EncodeErrors @@ -24,7 +26,6 @@ func (e *encoder) setError(namespace []byte, err error) { } func (e *encoder) setVal(namespace []byte, idx int, vals ...string) { - arr, ok := e.values[string(namespace)] if ok { arr = append(arr, vals...) @@ -36,7 +37,6 @@ func (e *encoder) setVal(namespace []byte, idx int, vals ...string) { } func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) { - typ := v.Type() l := len(namespace) first := l == 0 @@ -69,7 +69,6 @@ func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) { } func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx int, isOmitEmpty bool) { - if idx > -1 && current.Kind() == reflect.Ptr { namespace = append(namespace, '[') namespace = strconv.AppendInt(namespace, int64(idx), 10) @@ -83,7 +82,6 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in v, kind := ExtractType(current) if e.e.customTypeFuncs != nil { - if cf, ok := e.e.customTypeFuncs[v.Type()]; ok { arr, err := cf(v.Interface()) @@ -103,6 +101,13 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in } } + if encoded, err := e.marshal(namespace, v, idx); err != nil { + e.setError(namespace, err) + return + } else if encoded { + return + } + switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: return @@ -216,11 +221,9 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in } func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) { - v, kind := ExtractType(key) if e.e.customTypeFuncs != nil { - if cf, ok := e.e.customTypeFuncs[v.Type()]; ok { arr, err := cf(v.Interface()) if err != nil { @@ -259,3 +262,40 @@ func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) return "", false } } + +func (e *encoder) marshal(namespace []byte, v reflect.Value, idx int) (bool, error) { + t := v.Type() + if t.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(t).Implements(marshalerType) { + return e.marshalAddr(namespace, v, idx) + } + if !t.Implements(marshalerType) && !reflect.PtrTo(t).Implements(marshalerType) { + return false, nil + } + if t.Kind() == reflect.Ptr && v.IsNil() { + return false, nil + } + um, ok := v.Interface().(Marshaler) + if !ok { + return false, nil + } + vals, err := um.MarshalForm() + if err != nil { + return false, err + } + e.setVal(namespace, idx, vals...) + return true, nil +} + +func (e *encoder) marshalAddr(namespace []byte, v reflect.Value, idx int) (bool, error) { + va := v.Addr() + if va.IsNil() { + return false, nil + } + m := va.Interface().(Marshaler) + vals, err := m.MarshalForm() + if err != nil { + return false, err + } + e.setVal(namespace, idx, vals...) + return true, nil +} diff --git a/encoder_test.go b/encoder_test.go index 3120efd..c4c88f9 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -2,6 +2,7 @@ package form import ( "errors" + "net/url" "reflect" "strings" "testing" @@ -29,7 +30,6 @@ import ( // go test -memprofile mem.out func TestEncoderInt(t *testing.T) { - type TestInt struct { Int int Int8 int8 @@ -194,7 +194,6 @@ func TestEncoderInt(t *testing.T) { } func TestEncoderUint(t *testing.T) { - type TestUint struct { Uint uint Uint8 uint8 @@ -360,7 +359,6 @@ func TestEncoderUint(t *testing.T) { } func TestEncoderString(t *testing.T) { - type TestString struct { String string StringPtr *string @@ -487,7 +485,6 @@ func TestEncoderString(t *testing.T) { } func TestEncoderFloat(t *testing.T) { - type TestFloat struct { Float32 float32 Float32Ptr *float32 @@ -706,7 +703,6 @@ func TestEncoderFloat(t *testing.T) { } func TestEncoderBool(t *testing.T) { - type TestBool struct { Bool bool BoolPtr *bool @@ -820,7 +816,6 @@ func TestEncoderBool(t *testing.T) { } func TestEncoderStruct(t *testing.T) { - type Phone struct { Number string } @@ -1019,7 +1014,6 @@ func TestEncoderStruct(t *testing.T) { } func TestEncoderStructCustomNamespace(t *testing.T) { - type Phone struct { Number string } @@ -1237,7 +1231,6 @@ func TestEncoderMap(t *testing.T) { } func TestDecodeAllNonStructTypes(t *testing.T) { - encoder := NewEncoder() // test integers @@ -1351,7 +1344,6 @@ func TestDecodeAllNonStructTypes(t *testing.T) { } func TestEncoderNativeTime(t *testing.T) { - type TestError struct { Time time.Time TimeNoValue time.Time @@ -1378,7 +1370,6 @@ func TestEncoderNativeTime(t *testing.T) { } func TestEncoderErrors(t *testing.T) { - tm, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") Equal(t, err, nil) @@ -1422,7 +1413,6 @@ func TestEncoderErrors(t *testing.T) { } func TestEncoderPanicsAndBadValues(t *testing.T) { - encoder := NewEncoder() values, err := encoder.Encode(nil) @@ -1449,7 +1439,6 @@ func TestEncoderPanicsAndBadValues(t *testing.T) { } func TestEncoderExplicit(t *testing.T) { - type Test struct { Name string `form:"Name"` Age int @@ -1470,7 +1459,6 @@ func TestEncoderExplicit(t *testing.T) { } func TestEncoderRegisterTagNameFunc(t *testing.T) { - type Test struct { Name string `json:"name"` Age int `json:"-"` @@ -1499,7 +1487,6 @@ func TestEncoderRegisterTagNameFunc(t *testing.T) { } func TestEncoderEmbedModes(t *testing.T) { - type A struct { Field string } @@ -1533,19 +1520,18 @@ func TestEncoderEmbedModes(t *testing.T) { } func TestOmitEmpty(t *testing.T) { - type NotComparable struct { Slice []string } type Test struct { - String string `form:",omitempty"` - Array []string `form:",omitempty"` - Map map[string]string `form:",omitempty"` - String2 string `form:"str,omitempty"` - Array2 []string `form:"arr,omitempty"` - Map2 map[string]string `form:"map,omitempty"` - NotComparable `form:",omitempty"` + String string `form:",omitempty"` + Array []string `form:",omitempty"` + Map map[string]string `form:",omitempty"` + String2 string `form:"str,omitempty"` + Array2 []string `form:"arr,omitempty"` + Map2 map[string]string `form:"map,omitempty"` + NotComparable `form:",omitempty"` } var tst Test @@ -1607,3 +1593,175 @@ func TestOmitEmpty(t *testing.T) { Equal(t, values["x"][0], "0") Equal(t, values["arr[0]"][0], "") } + +type marshaler struct { + Fname string + Sname string +} + +func (m marshaler) MarshalForm() ([]string, error) { + return []string{ + m.Fname, + m.Sname, + }, nil +} + +func Test_MarshalForm(t *testing.T) { + T1 := struct { + Ptr *marshaler + NilPtr *marshaler + Struct marshaler + Slice []marshaler + SlicePtr []*marshaler + Map map[string]marshaler + MapPtr map[string]*marshaler + }{ + Ptr: &marshaler{ + Fname: "ptrfname", + Sname: "ptrsname", + }, + Struct: marshaler{ + Fname: "structfname", + Sname: "structsname", + }, + Slice: []marshaler{{ + Fname: "slice0fname", + Sname: "slice0sname", + }, { + Fname: "slice1fname", + Sname: "slice1sname", + }}, + SlicePtr: []*marshaler{{ + Fname: "sliceptr0fname", + Sname: "sliceptr0sname", + }, { + Fname: "sliceptr1fname", + Sname: "sliceptr1sname", + }}, + Map: map[string]marshaler{ + "key1": { + Fname: "mapk1fname", + Sname: "mapk1sname", + }, + "key2": { + Fname: "mapk2fname", + Sname: "mapk2sname", + }, + }, + MapPtr: map[string]*marshaler{ + "key1": { + Fname: "mapptrk1fname", + Sname: "mapptrk1sname", + }, + "key2": { + Fname: "mapptrk2fname", + Sname: "mapptrk2sname", + }, + }, + } + values, err := NewEncoder().Encode(T1) + Equal(t, err, nil) + Equal(t, values["Ptr"], []string{"ptrfname", "ptrsname"}) + Equal(t, values["NilPointer"], nil) + Equal(t, values["Struct"], []string{"structfname", "structsname"}) + Equal(t, values["Slice"], []string{"slice0fname", "slice0sname", "slice1fname", "slice1sname"}) + Equal(t, values["SlicePtr[0]"], []string{"sliceptr0fname", "sliceptr0sname"}) + Equal(t, values["SlicePtr[1]"], []string{"sliceptr1fname", "sliceptr1sname"}) + Equal(t, values["Map[key1]"], []string{"mapk1fname", "mapk1sname"}) + Equal(t, values["Map[key2]"], []string{"mapk2fname", "mapk2sname"}) + Equal(t, values["MapPtr[key1]"], []string{"mapptrk1fname", "mapptrk1sname"}) + Equal(t, values["MapPtr[key2]"], []string{"mapptrk2fname", "mapptrk2sname"}) +} + +type errmarshaler struct { + Fname string + Sname string +} + +func (m errmarshaler) MarshalForm() ([]string, error) { + return nil, errors.New("always err") +} + +func Test_MarshalForm_Err(t *testing.T) { + encoder := NewEncoder() + + t1 := struct { + Ptr *errmarshaler + }{ + Ptr: &errmarshaler{ + Fname: "John", + Sname: "Smith", + }, + } + v1, err := encoder.Encode(t1) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:Ptr ERROR:always err") + Equal(t, v1, url.Values{}) + + t2 := struct { + NilPtr *errmarshaler + }{} + v2, err := encoder.Encode(t2) + Equal(t, err, nil) + Equal(t, v2, url.Values{}) + + t3 := struct { + Struct errmarshaler + }{ + Struct: errmarshaler{ + Fname: "John", + Sname: "Smith", + }, + } + v3, err := encoder.Encode(t3) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:Struct ERROR:always err") + Equal(t, v3, url.Values{}) +} + +type errmarshalerptrrec struct { + called bool + Fname string + Sname string +} + +func (m *errmarshalerptrrec) MarshalForm() ([]string, error) { + m.called = true + return nil, errors.New("always err") +} + +func Test_MarshalForm_ErrPtrRec(t *testing.T) { + encoder := NewEncoder() + + t1 := struct { + Ptr *errmarshalerptrrec + }{ + Ptr: &errmarshalerptrrec{ + Fname: "John", + Sname: "Smith", + }, + } + v1, err := encoder.Encode(t1) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:Ptr ERROR:always err") + Equal(t, v1, url.Values{}) + + t2 := struct { + NilPtr *errmarshalerptrrec + }{} + v2, err := encoder.Encode(t2) + Equal(t, err, nil) + Equal(t, v2, url.Values{}) + + t3 := struct { + Struct errmarshalerptrrec + }{ + Struct: errmarshalerptrrec{ + Fname: "John", + Sname: "Smith", + }, + } + _, err = encoder.Encode(t3) + Equal(t, err, nil) + Equal(t, t3.Struct.called, false) +} diff --git a/form_decoder.go b/form_decoder.go index ac131ea..7cd41e1 100644 --- a/form_decoder.go +++ b/form_decoder.go @@ -8,6 +8,11 @@ import ( "sync" ) +// Unmarshaler is the interface implemented by an object that can unmarshal a form representation of itself. +type Unmarshaler interface { + UnmarshalForm([]string) error +} + // DecodeCustomTypeFunc allows for registering/overriding types to be parsed. type DecodeCustomTypeFunc func([]string) (interface{}, error) @@ -35,7 +40,6 @@ type InvalidDecoderError struct { } func (e *InvalidDecoderError) Error() string { - if e.Type == nil { return "form: Decode(nil)" } @@ -75,7 +79,6 @@ type Decoder struct { // NewDecoder creates a new decoder instance with sane defaults func NewDecoder() *Decoder { - d := &Decoder{ tagName: "form", mode: ModeImplicit, @@ -142,7 +145,6 @@ func (d *Decoder) RegisterTagNameFunc(fn TagNameFunc) { // the struct and not just the struct fields eg. url.Values{"User":"Name%3Djoeybloggs"} will call the // custom type function with `User` as the type, however url.Values{"User.Name":"joeybloggs"} will not. func (d *Decoder) RegisterCustomTypeFunc(fn DecodeCustomTypeFunc, types ...interface{}) { - if d.customTypeFuncs == nil { d.customTypeFuncs = map[reflect.Type]DecodeCustomTypeFunc{} } @@ -156,7 +158,6 @@ func (d *Decoder) RegisterCustomTypeFunc(fn DecodeCustomTypeFunc, types ...inter // // Decode returns an InvalidDecoderError if interface passed is invalid. func (d *Decoder) Decode(v interface{}, values url.Values) (err error) { - val := reflect.ValueOf(v) if val.Kind() != reflect.Ptr || val.IsNil() { diff --git a/form_encoder.go b/form_encoder.go index 48960c9..6ffe056 100644 --- a/form_encoder.go +++ b/form_encoder.go @@ -8,6 +8,11 @@ import ( "sync" ) +// Marshaler is the interface implemented by an object that can marshal itself into a textual form. +type Marshaler interface { + MarshalForm() ([]string, error) +} + // EncodeCustomTypeFunc allows for registering/overriding types to be parsed. type EncodeCustomTypeFunc func(x interface{}) ([]string, error) @@ -34,7 +39,6 @@ type InvalidEncodeError struct { } func (e *InvalidEncodeError) Error() string { - if e.Type == nil { return "form: Encode(nil)" } @@ -56,7 +60,6 @@ type Encoder struct { // NewEncoder creates a new encoder instance with sane defaults func NewEncoder() *Encoder { - e := &Encoder{ tagName: "form", mode: ModeImplicit, @@ -116,7 +119,6 @@ func (e *Encoder) RegisterTagNameFunc(fn TagNameFunc) { // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types // NOTE: this method is not thread-safe it is intended that these all be registered prior to any parsing func (e *Encoder) RegisterCustomTypeFunc(fn EncodeCustomTypeFunc, types ...interface{}) { - if e.customTypeFuncs == nil { e.customTypeFuncs = map[reflect.Type]EncodeCustomTypeFunc{} } @@ -128,7 +130,6 @@ func (e *Encoder) RegisterCustomTypeFunc(fn EncodeCustomTypeFunc, types ...inter // Encode encodes the given values and sets the corresponding struct values func (e *Encoder) Encode(v interface{}) (values url.Values, err error) { - val, kind := ExtractType(reflect.ValueOf(v)) if kind == reflect.Ptr || kind == reflect.Interface || kind == reflect.Invalid {