From 97397ebb7382fbd8671a5c95790ea1fdf772ca37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 12:00:24 +0100 Subject: [PATCH 01/13] add FormMarshaler and FormUnmarshaler --- decoder.go | 27 +++++++++++--- decoder_test.go | 97 +++++++++++++++++++++++++++++++++++++------------ encoder.go | 21 ++++++++--- encoder_test.go | 61 ++++++++++++++++++++----------- form_decoder.go | 9 +++-- form_encoder.go | 9 +++-- 6 files changed, 159 insertions(+), 65 deletions(-) diff --git a/decoder.go b/decoder.go index e212422..2c2e206 100644 --- a/decoder.go +++ b/decoder.go @@ -63,7 +63,6 @@ func (d *decoder) parseMapData() { } for i = 0; i < len(k); i++ { - switch k[i] { case '[': idx = i @@ -117,7 +116,6 @@ func (d *decoder) parseMapData() { if ke.ivalue > rd.sliceLen { rd.sliceLen = ke.ivalue - } } @@ -140,7 +138,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 +174,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 +194,27 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } } } + { + v := v // deliberately shadow v as to not modify + t := v.Type() + if t.Kind() != reflect.Ptr && v.CanAddr() { + v = v.Addr() + } + if um, ok := v.Interface().(FormUnmarshaler); ok { + // if receiver is a nil pointer, set before calling function. + if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { + t = t.Elem() + v.Set(reflect.New(t)) + um = v.Interface().(FormUnmarshaler) + } + if err = um.UnmarshalForm(arr[idx:]); err != nil { + d.setError(namespace, err) + return + } + set = true + return + } + } switch kind { case reflect.Interface: if !ok || idx == len(arr) { @@ -602,7 +618,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 { diff --git a/decoder_test.go b/decoder_test.go index 99897ed..dbb08a2 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,77 @@ 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 + } + + in := url.Values{ + "Ptr": []string{"Liam", "Neeson"}, + "NilPtr": []string{"John", "Smith"}, + "Struct": []string{"Bob", "Dylan"}, + } + + 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, "Liam") + Equal(t, v.Ptr.sname, "Neeson") + Equal(t, v.NilPtr.fname, "John") + Equal(t, v.NilPtr.sname, "Smith") + Equal(t, v.Struct.fname, "Bob") + Equal(t, v.Struct.sname, "Dylan") +} + +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..3adff69 100644 --- a/encoder.go +++ b/encoder.go @@ -24,7 +24,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 +35,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 +67,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 +80,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()) @@ -102,6 +98,21 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in return } } + { + v := v // deliberately shadow v as to not modify + if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { + return + } + if um, ok := v.Interface().(FormMarshaler); ok { + vals, err := um.MarshalForm() + if err != nil { + e.setError(namespace, err) + return + } + e.setVal(namespace, idx, vals...) + return + } + } switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: @@ -216,11 +227,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 { diff --git a/encoder_test.go b/encoder_test.go index 3120efd..560fde8 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -29,7 +29,6 @@ import ( // go test -memprofile mem.out func TestEncoderInt(t *testing.T) { - type TestInt struct { Int int Int8 int8 @@ -194,7 +193,6 @@ func TestEncoderInt(t *testing.T) { } func TestEncoderUint(t *testing.T) { - type TestUint struct { Uint uint Uint8 uint8 @@ -360,7 +358,6 @@ func TestEncoderUint(t *testing.T) { } func TestEncoderString(t *testing.T) { - type TestString struct { String string StringPtr *string @@ -487,7 +484,6 @@ func TestEncoderString(t *testing.T) { } func TestEncoderFloat(t *testing.T) { - type TestFloat struct { Float32 float32 Float32Ptr *float32 @@ -706,7 +702,6 @@ func TestEncoderFloat(t *testing.T) { } func TestEncoderBool(t *testing.T) { - type TestBool struct { Bool bool BoolPtr *bool @@ -820,7 +815,6 @@ func TestEncoderBool(t *testing.T) { } func TestEncoderStruct(t *testing.T) { - type Phone struct { Number string } @@ -1019,7 +1013,6 @@ func TestEncoderStruct(t *testing.T) { } func TestEncoderStructCustomNamespace(t *testing.T) { - type Phone struct { Number string } @@ -1237,7 +1230,6 @@ func TestEncoderMap(t *testing.T) { } func TestDecodeAllNonStructTypes(t *testing.T) { - encoder := NewEncoder() // test integers @@ -1351,7 +1343,6 @@ func TestDecodeAllNonStructTypes(t *testing.T) { } func TestEncoderNativeTime(t *testing.T) { - type TestError struct { Time time.Time TimeNoValue time.Time @@ -1378,7 +1369,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 +1412,6 @@ func TestEncoderErrors(t *testing.T) { } func TestEncoderPanicsAndBadValues(t *testing.T) { - encoder := NewEncoder() values, err := encoder.Encode(nil) @@ -1449,7 +1438,6 @@ func TestEncoderPanicsAndBadValues(t *testing.T) { } func TestEncoderExplicit(t *testing.T) { - type Test struct { Name string `form:"Name"` Age int @@ -1470,7 +1458,6 @@ func TestEncoderExplicit(t *testing.T) { } func TestEncoderRegisterTagNameFunc(t *testing.T) { - type Test struct { Name string `json:"name"` Age int `json:"-"` @@ -1499,7 +1486,6 @@ func TestEncoderRegisterTagNameFunc(t *testing.T) { } func TestEncoderEmbedModes(t *testing.T) { - type A struct { Field string } @@ -1533,19 +1519,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 +1592,35 @@ 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 + Struct marshaler + }{ + Ptr: &marshaler{ + Fname: "John", + Sname: "Smith", + }, + Struct: marshaler{ + Fname: "Bob", + Sname: "Dylan", + }, + } + values, err := NewEncoder().Encode(T1) + Equal(t, err, nil) + Equal(t, values["Ptr"], []string{"John", "Smith"}) + Equal(t, values["Struct"], []string{"Bob", "Dylan"}) +} diff --git a/form_decoder.go b/form_decoder.go index ac131ea..cb2a9ae 100644 --- a/form_decoder.go +++ b/form_decoder.go @@ -8,6 +8,11 @@ import ( "sync" ) +// FormUnmarshaler is the interface implemented by an object that can unmarshal a form representation of itself. +type FormUnmarshaler interface { + UnmarshalForm(ss []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..5454a21 100644 --- a/form_encoder.go +++ b/form_encoder.go @@ -8,6 +8,11 @@ import ( "sync" ) +// FormMarshaler is the interface implemented by an object that can marshal itself into a textual form. +type FormMarshaler 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 { From 2d15be0857b85f6bece1c19b27e2dcff06026ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 12:09:55 +0100 Subject: [PATCH 02/13] encoder error tests --- encoder_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/encoder_test.go b/encoder_test.go index 560fde8..a5f5ace 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -2,6 +2,7 @@ package form import ( "errors" + "net/url" "reflect" "strings" "testing" @@ -1608,6 +1609,7 @@ func (m marshaler) MarshalForm() ([]string, error) { func Test_MarshalForm(t *testing.T) { T1 := struct { Ptr *marshaler + NilPtr *marshaler Struct marshaler }{ Ptr: &marshaler{ @@ -1622,5 +1624,52 @@ func Test_MarshalForm(t *testing.T) { values, err := NewEncoder().Encode(T1) Equal(t, err, nil) Equal(t, values["Ptr"], []string{"John", "Smith"}) + Equal(t, values["NilPointer"], nil) Equal(t, values["Struct"], []string{"Bob", "Dylan"}) } + +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: "Bob", + Sname: "Dylan", + }, + } + v3, err := encoder.Encode(t3) + NotEqual(t, err, nil) + Equal(t, err.Error(), "Field Namespace:Struct ERROR:always err") + Equal(t, v3, url.Values{}) +} From ab25a15957b916733467bc777c930a8b756f8185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 12:15:45 +0100 Subject: [PATCH 03/13] rename marshaller interface to prevent stutter --- decoder.go | 4 ++-- encoder.go | 2 +- form_decoder.go | 4 ++-- form_encoder.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/decoder.go b/decoder.go index 2c2e206..fa7fe33 100644 --- a/decoder.go +++ b/decoder.go @@ -200,12 +200,12 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in if t.Kind() != reflect.Ptr && v.CanAddr() { v = v.Addr() } - if um, ok := v.Interface().(FormUnmarshaler); ok { + if um, ok := v.Interface().(Unmarshaler); ok { // if receiver is a nil pointer, set before calling function. if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { t = t.Elem() v.Set(reflect.New(t)) - um = v.Interface().(FormUnmarshaler) + um = v.Interface().(Unmarshaler) } if err = um.UnmarshalForm(arr[idx:]); err != nil { d.setError(namespace, err) diff --git a/encoder.go b/encoder.go index 3adff69..71857ea 100644 --- a/encoder.go +++ b/encoder.go @@ -103,7 +103,7 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { return } - if um, ok := v.Interface().(FormMarshaler); ok { + if um, ok := v.Interface().(Marshaler); ok { vals, err := um.MarshalForm() if err != nil { e.setError(namespace, err) diff --git a/form_decoder.go b/form_decoder.go index cb2a9ae..bacfc6a 100644 --- a/form_decoder.go +++ b/form_decoder.go @@ -8,8 +8,8 @@ import ( "sync" ) -// FormUnmarshaler is the interface implemented by an object that can unmarshal a form representation of itself. -type FormUnmarshaler interface { +// Unmarshaler is the interface implemented by an object that can unmarshal a form representation of itself. +type Unmarshaler interface { UnmarshalForm(ss []string) error } diff --git a/form_encoder.go b/form_encoder.go index 5454a21..6ffe056 100644 --- a/form_encoder.go +++ b/form_encoder.go @@ -8,8 +8,8 @@ import ( "sync" ) -// FormMarshaler is the interface implemented by an object that can marshal itself into a textual form. -type FormMarshaler interface { +// Marshaler is the interface implemented by an object that can marshal itself into a textual form. +type Marshaler interface { MarshalForm() ([]string, error) } From ef112dc3227edb55df8b9108e5cd534521db22d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 12:16:02 +0100 Subject: [PATCH 04/13] update README --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) 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 From eab201f3a6988e85b9f50e21215e9720890cefe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 12:29:16 +0100 Subject: [PATCH 05/13] remove unnecessary shadow in encoder, use already define var in decoder --- decoder.go | 2 +- encoder.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/decoder.go b/decoder.go index fa7fe33..ea5cb1b 100644 --- a/decoder.go +++ b/decoder.go @@ -202,7 +202,7 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } if um, ok := v.Interface().(Unmarshaler); ok { // if receiver is a nil pointer, set before calling function. - if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { + if t.Kind() == reflect.Ptr && v.IsNil() { t = t.Elem() v.Set(reflect.New(t)) um = v.Interface().(Unmarshaler) diff --git a/encoder.go b/encoder.go index 71857ea..088959c 100644 --- a/encoder.go +++ b/encoder.go @@ -99,7 +99,6 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in } } { - v := v // deliberately shadow v as to not modify if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { return } From e904ed6ec570fffe21389d55343e338185bcc8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 12:58:13 +0100 Subject: [PATCH 06/13] slice tests --- encoder_test.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/encoder_test.go b/encoder_test.go index a5f5ace..1b6fa7e 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1608,9 +1608,11 @@ func (m marshaler) MarshalForm() ([]string, error) { func Test_MarshalForm(t *testing.T) { T1 := struct { - Ptr *marshaler - NilPtr *marshaler - Struct marshaler + Ptr *marshaler + NilPtr *marshaler + Struct marshaler + Slice []*marshaler + SlicePtr []*marshaler }{ Ptr: &marshaler{ Fname: "John", @@ -1620,12 +1622,30 @@ func Test_MarshalForm(t *testing.T) { Fname: "Bob", Sname: "Dylan", }, + Slice: []*marshaler{{ + Fname: "Danny", + Sname: "Devito", + }, { + Fname: "Arnold", + Sname: "Schwarzenegger", + }}, + SlicePtr: []*marshaler{{ + Fname: "Mary-Kate", + Sname: "Olsen", + }, { + Fname: "Ashley", + Sname: "Olsen", + }}, } values, err := NewEncoder().Encode(T1) Equal(t, err, nil) Equal(t, values["Ptr"], []string{"John", "Smith"}) Equal(t, values["NilPointer"], nil) Equal(t, values["Struct"], []string{"Bob", "Dylan"}) + Equal(t, values["Slice[0]"], []string{"Danny", "Devito"}) + Equal(t, values["Slice[1]"], []string{"Arnold", "Schwarzenegger"}) + Equal(t, values["SlicePtr[0]"], []string{"Mary-Kate", "Olsen"}) + Equal(t, values["SlicePtr[1]"], []string{"Ashley", "Olsen"}) } type errmarshaler struct { From 6270911f032ff1f5e6c43b02024da51a3be8e67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 13:37:12 +0100 Subject: [PATCH 07/13] fix pointer test --- encoder_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/encoder_test.go b/encoder_test.go index 1b6fa7e..42b857b 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1611,7 +1611,7 @@ func Test_MarshalForm(t *testing.T) { Ptr *marshaler NilPtr *marshaler Struct marshaler - Slice []*marshaler + Slice []marshaler SlicePtr []*marshaler }{ Ptr: &marshaler{ @@ -1622,7 +1622,7 @@ func Test_MarshalForm(t *testing.T) { Fname: "Bob", Sname: "Dylan", }, - Slice: []*marshaler{{ + Slice: []marshaler{{ Fname: "Danny", Sname: "Devito", }, { @@ -1642,8 +1642,7 @@ func Test_MarshalForm(t *testing.T) { Equal(t, values["Ptr"], []string{"John", "Smith"}) Equal(t, values["NilPointer"], nil) Equal(t, values["Struct"], []string{"Bob", "Dylan"}) - Equal(t, values["Slice[0]"], []string{"Danny", "Devito"}) - Equal(t, values["Slice[1]"], []string{"Arnold", "Schwarzenegger"}) + Equal(t, values["Slice"], []string{"Danny", "Devito", "Arnold", "Schwarzenegger"}) Equal(t, values["SlicePtr[0]"], []string{"Mary-Kate", "Olsen"}) Equal(t, values["SlicePtr[1]"], []string{"Ashley", "Olsen"}) } From 54d6dbf0f1af9c0b474b7ade9ecc6b2b4fd2688b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 13:57:58 +0100 Subject: [PATCH 08/13] map encoding tests --- encoder_test.go | 60 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/encoder_test.go b/encoder_test.go index 42b857b..013109b 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1613,38 +1613,64 @@ func Test_MarshalForm(t *testing.T) { Struct marshaler Slice []marshaler SlicePtr []*marshaler + Map map[string]marshaler + MapPtr map[string]*marshaler }{ Ptr: &marshaler{ - Fname: "John", - Sname: "Smith", + Fname: "ptrfname", + Sname: "ptrsname", }, Struct: marshaler{ - Fname: "Bob", - Sname: "Dylan", + Fname: "structfname", + Sname: "structsname", }, Slice: []marshaler{{ - Fname: "Danny", - Sname: "Devito", + Fname: "slice1fname", + Sname: "slice1sname", }, { - Fname: "Arnold", - Sname: "Schwarzenegger", + Fname: "slice2fname", + Sname: "slice2sname", }}, SlicePtr: []*marshaler{{ - Fname: "Mary-Kate", - Sname: "Olsen", + Fname: "sliceptr1fname", + Sname: "sliceptr1sname", }, { - Fname: "Ashley", - Sname: "Olsen", + Fname: "sliceptr2fname", + Sname: "sliceptr2sname", }}, + 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{"John", "Smith"}) + Equal(t, values["Ptr"], []string{"ptrfname", "ptrsname"}) Equal(t, values["NilPointer"], nil) - Equal(t, values["Struct"], []string{"Bob", "Dylan"}) - Equal(t, values["Slice"], []string{"Danny", "Devito", "Arnold", "Schwarzenegger"}) - Equal(t, values["SlicePtr[0]"], []string{"Mary-Kate", "Olsen"}) - Equal(t, values["SlicePtr[1]"], []string{"Ashley", "Olsen"}) + Equal(t, values["Struct"], []string{"structfname", "structsname"}) + Equal(t, values["Slice"], []string{"slice1fname", "slice1sname", "slice2fname", "slice2sname"}) + Equal(t, values["SlicePtr[0]"], []string{"sliceptr1fname", "sliceptr1sname"}) + Equal(t, values["SlicePtr[1]"], []string{"sliceptr2fname", "sliceptr2sname"}) + 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 { From bc69006b7580c8d42fedd1286cd4dd6e21c75db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Thu, 9 May 2024 14:10:26 +0100 Subject: [PATCH 09/13] map encoder tests, more decoder tests --- decoder_test.go | 56 ++++++++++++++++++++++++++++++++++++++----------- encoder_test.go | 18 ++++++++-------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/decoder_test.go b/decoder_test.go index dbb08a2..2ace30d 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -1930,15 +1930,27 @@ func (u *unmarshaler) UnmarshalForm(ss []string) error { func TestDecoder_UnmarshalForm(t *testing.T) { type T1 struct { - Ptr *unmarshaler - NilPtr *unmarshaler - Struct unmarshaler + Ptr *unmarshaler + NilPtr *unmarshaler + Struct unmarshaler + Slice []unmarshaler + SlicePtr []*unmarshaler + Map map[string]unmarshaler + MapPtr map[string]*unmarshaler } in := url.Values{ - "Ptr": []string{"Liam", "Neeson"}, - "NilPtr": []string{"John", "Smith"}, - "Struct": []string{"Bob", "Dylan"}, + "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) @@ -1946,12 +1958,32 @@ func TestDecoder_UnmarshalForm(t *testing.T) { err := NewDecoder().Decode(v, in) Equal(t, err, nil) NotEqual(t, v.NilPtr, nil) - Equal(t, v.Ptr.fname, "Liam") - Equal(t, v.Ptr.sname, "Neeson") - Equal(t, v.NilPtr.fname, "John") - Equal(t, v.NilPtr.sname, "Smith") - Equal(t, v.Struct.fname, "Bob") - Equal(t, v.Struct.sname, "Dylan") + 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) { diff --git a/encoder_test.go b/encoder_test.go index 013109b..84957c1 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1625,18 +1625,18 @@ func Test_MarshalForm(t *testing.T) { Sname: "structsname", }, Slice: []marshaler{{ + Fname: "slice0fname", + Sname: "slice0sname", + }, { Fname: "slice1fname", Sname: "slice1sname", - }, { - Fname: "slice2fname", - Sname: "slice2sname", }}, SlicePtr: []*marshaler{{ + Fname: "sliceptr0fname", + Sname: "sliceptr0sname", + }, { Fname: "sliceptr1fname", Sname: "sliceptr1sname", - }, { - Fname: "sliceptr2fname", - Sname: "sliceptr2sname", }}, Map: map[string]marshaler{ "key1": { @@ -1664,9 +1664,9 @@ func Test_MarshalForm(t *testing.T) { Equal(t, values["Ptr"], []string{"ptrfname", "ptrsname"}) Equal(t, values["NilPointer"], nil) Equal(t, values["Struct"], []string{"structfname", "structsname"}) - Equal(t, values["Slice"], []string{"slice1fname", "slice1sname", "slice2fname", "slice2sname"}) - Equal(t, values["SlicePtr[0]"], []string{"sliceptr1fname", "sliceptr1sname"}) - Equal(t, values["SlicePtr[1]"], []string{"sliceptr2fname", "sliceptr2sname"}) + 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"}) From 58d5d48dcb3adb78f5db53b21d6f6019149ea9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Mon, 13 May 2024 15:55:28 +0100 Subject: [PATCH 10/13] standard lib ideas, tidier handling --- decoder.go | 63 +++++++++++++++++++++++++++++++++---------------- encoder.go | 54 ++++++++++++++++++++++++++++++++---------- encoder_test.go | 4 ++-- form_decoder.go | 2 +- 4 files changed, 87 insertions(+), 36 deletions(-) diff --git a/decoder.go b/decoder.go index ea5cb1b..f626521 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" @@ -194,27 +196,14 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in } } } - { - v := v // deliberately shadow v as to not modify - t := v.Type() - if t.Kind() != reflect.Ptr && v.CanAddr() { - v = v.Addr() - } - if um, ok := v.Interface().(Unmarshaler); ok { - // if receiver is a nil pointer, set before calling function. - if t.Kind() == reflect.Ptr && v.IsNil() { - t = t.Elem() - v.Set(reflect.New(t)) - um = v.Interface().(Unmarshaler) - } - if err = um.UnmarshalForm(arr[idx:]); err != nil { - d.setError(namespace, err) - return - } - set = true - return - } + + 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) { @@ -765,3 +754,37 @@ 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()) + if err := nv.Interface().(Unmarshaler).UnmarshalForm(arr[idx:]); err != nil { + return false, err + } + v.Set(nv) + return true, nil +} diff --git a/encoder.go b/encoder.go index 088959c..6a9d7e1 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 @@ -98,19 +100,9 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in return } } - { - if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { - return - } - if um, ok := v.Interface().(Marshaler); ok { - vals, err := um.MarshalForm() - if err != nil { - e.setError(namespace, err) - return - } - e.setVal(namespace, idx, vals...) - return - } + + if ok := e.marshal(namespace, v, idx); ok { + return } switch kind { @@ -267,3 +259,39 @@ 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 { + t := v.Type() + if t.Kind() != reflect.Pointer && v.CanAddr() && reflect.PointerTo(t).Implements(marshalerType) { + return e.marshalAddr(namespace, v, idx) + } + if !t.Implements(marshalerType) && !reflect.PointerTo(t).Implements(marshalerType) { + return false + } + if t.Kind() == reflect.Pointer && v.IsNil() { + return false + } + um := v.Interface().(Marshaler) + vals, err := um.MarshalForm() + if err != nil { + e.setError(namespace, err) + return true + } + e.setVal(namespace, idx, vals...) + return true +} + +func (e *encoder) marshalAddr(namespace []byte, v reflect.Value, idx int) bool { + va := v.Addr() + if va.IsNil() { + return false + } + um := va.Interface().(Marshaler) + vals, err := um.MarshalForm() + if err != nil { + e.setError(namespace, err) + return true + } + e.setVal(namespace, idx, vals...) + return true +} diff --git a/encoder_test.go b/encoder_test.go index 84957c1..fd32882 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1709,8 +1709,8 @@ func Test_MarshalForm_Err(t *testing.T) { Struct errmarshaler }{ Struct: errmarshaler{ - Fname: "Bob", - Sname: "Dylan", + Fname: "John", + Sname: "Smith", }, } v3, err := encoder.Encode(t3) diff --git a/form_decoder.go b/form_decoder.go index bacfc6a..7cd41e1 100644 --- a/form_decoder.go +++ b/form_decoder.go @@ -10,7 +10,7 @@ import ( // Unmarshaler is the interface implemented by an object that can unmarshal a form representation of itself. type Unmarshaler interface { - UnmarshalForm(ss []string) error + UnmarshalForm([]string) error } // DecodeCustomTypeFunc allows for registering/overriding types to be parsed. From 8eda55b4feb2661b18ee8c9fe2fa448f90630dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Mon, 13 May 2024 16:03:34 +0100 Subject: [PATCH 11/13] have the encoder marshal funcs return an error --- decoder.go | 3 ++- encoder.go | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/decoder.go b/decoder.go index f626521..72c470e 100644 --- a/decoder.go +++ b/decoder.go @@ -782,7 +782,8 @@ func (d *decoder) unmarshal(v reflect.Value, idx int, arr []string) (bool, error func (d *decoder) unmarshalAddr(v reflect.Value, idx int, arr []string) (bool, error) { nv := reflect.New(v.Type().Elem()) - if err := nv.Interface().(Unmarshaler).UnmarshalForm(arr[idx:]); err != nil { + um := nv.Interface().(Unmarshaler) + if err := um.UnmarshalForm(arr[idx:]); err != nil { return false, err } v.Set(nv) diff --git a/encoder.go b/encoder.go index 6a9d7e1..ddb3f66 100644 --- a/encoder.go +++ b/encoder.go @@ -101,7 +101,10 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in } } - if ok := e.marshal(namespace, v, idx); ok { + if encoded, err := e.marshal(namespace, v, idx); err != nil { + e.setError(namespace, err) + return + } else if encoded { return } @@ -260,38 +263,36 @@ func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) } } -func (e *encoder) marshal(namespace []byte, v reflect.Value, idx int) bool { +func (e *encoder) marshal(namespace []byte, v reflect.Value, idx int) (bool, error) { t := v.Type() if t.Kind() != reflect.Pointer && v.CanAddr() && reflect.PointerTo(t).Implements(marshalerType) { return e.marshalAddr(namespace, v, idx) } if !t.Implements(marshalerType) && !reflect.PointerTo(t).Implements(marshalerType) { - return false + return false, nil } if t.Kind() == reflect.Pointer && v.IsNil() { - return false + return false, nil } um := v.Interface().(Marshaler) vals, err := um.MarshalForm() if err != nil { - e.setError(namespace, err) - return true + return false, err } e.setVal(namespace, idx, vals...) - return true + return true, nil } -func (e *encoder) marshalAddr(namespace []byte, v reflect.Value, idx int) bool { +func (e *encoder) marshalAddr(namespace []byte, v reflect.Value, idx int) (bool, error) { va := v.Addr() if va.IsNil() { - return false + return false, nil } - um := va.Interface().(Marshaler) - vals, err := um.MarshalForm() + m := va.Interface().(Marshaler) + vals, err := m.MarshalForm() if err != nil { - e.setError(namespace, err) - return true + return false, err } e.setVal(namespace, idx, vals...) - return true + return true, nil } From 98b9046168710434aea166d15f0ec91b3065e4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Mon, 13 May 2024 16:56:00 +0100 Subject: [PATCH 12/13] use legacy functions --- encoder.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/encoder.go b/encoder.go index ddb3f66..ae3eb81 100644 --- a/encoder.go +++ b/encoder.go @@ -265,13 +265,13 @@ func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) func (e *encoder) marshal(namespace []byte, v reflect.Value, idx int) (bool, error) { t := v.Type() - if t.Kind() != reflect.Pointer && v.CanAddr() && reflect.PointerTo(t).Implements(marshalerType) { + if t.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(t).Implements(marshalerType) { return e.marshalAddr(namespace, v, idx) } - if !t.Implements(marshalerType) && !reflect.PointerTo(t).Implements(marshalerType) { + if !t.Implements(marshalerType) && !reflect.PtrTo(t).Implements(marshalerType) { return false, nil } - if t.Kind() == reflect.Pointer && v.IsNil() { + if t.Kind() == reflect.Ptr && v.IsNil() { return false, nil } um := v.Interface().(Marshaler) From b0456709f41acee07522c41a3b245d5fc70719df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tighearn=C3=A1n=20Carroll?= Date: Mon, 13 May 2024 18:00:14 +0100 Subject: [PATCH 13/13] found, fixed, and tested a nil panic --- encoder.go | 5 ++++- encoder_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/encoder.go b/encoder.go index ae3eb81..2ab8468 100644 --- a/encoder.go +++ b/encoder.go @@ -274,7 +274,10 @@ func (e *encoder) marshal(namespace []byte, v reflect.Value, idx int) (bool, err if t.Kind() == reflect.Ptr && v.IsNil() { return false, nil } - um := v.Interface().(Marshaler) + um, ok := v.Interface().(Marshaler) + if !ok { + return false, nil + } vals, err := um.MarshalForm() if err != nil { return false, err diff --git a/encoder_test.go b/encoder_test.go index fd32882..c4c88f9 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1718,3 +1718,50 @@ func Test_MarshalForm_Err(t *testing.T) { 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) +}