From 13786c0d0ee8d2ad947388ce695c7fc6bc54bb88 Mon Sep 17 00:00:00 2001 From: KianYang-Lee Date: Thu, 19 Jun 2025 12:37:30 +0800 Subject: [PATCH 1/5] feat: add StringToMailAddressHookFunc --- decode_hooks.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/decode_hooks.go b/decode_hooks.go index a852a0a0..f732a639 100644 --- a/decode_hooks.go +++ b/decode_hooks.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "net/mail" "net/netip" "net/url" "reflect" @@ -712,3 +713,31 @@ func StringToComplex128HookFunc() DecodeHookFunc { return c128, wrapStrconvNumError(err) } } + +// StringToMailAddressHookFunc returns a DecodeHookFunc that converts +// strings to mail.Address. +func StringToMailAddressHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(mail.Address{}) { + return data, nil + } + + // Convert it by parsing + addr, err := mail.ParseAddress(data.(string)) + if err != nil { + return mail.Address{}, fmt.Errorf("failed parsing mail address %v: %w", data, err) + } + + if addr == nil { + return mail.Address{}, fmt.Errorf("failed parsing mail address %v", data) + } + + return *addr, nil + } +} From d6c131c9f7c3d9046c3e0d5ce1e9df1579d0f9b1 Mon Sep 17 00:00:00 2001 From: KianYang-Lee Date: Wed, 16 Jul 2025 10:19:33 +0800 Subject: [PATCH 2/5] fix formatting --- decode_hooks.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/decode_hooks.go b/decode_hooks.go index f732a639..90c1b45b 100644 --- a/decode_hooks.go +++ b/decode_hooks.go @@ -717,10 +717,7 @@ func StringToComplex128HookFunc() DecodeHookFunc { // StringToMailAddressHookFunc returns a DecodeHookFunc that converts // strings to mail.Address. func StringToMailAddressHookFunc() DecodeHookFunc { - return func( - f reflect.Type, - t reflect.Type, - data interface{}) (interface{}, error) { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String { return data, nil } From 2d5035f1ff9f692c3bc7707c18b9b99faecc7ecd Mon Sep 17 00:00:00 2001 From: KianYang-Lee Date: Wed, 16 Jul 2025 10:40:09 +0800 Subject: [PATCH 3/5] add tests --- decode_hooks_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/decode_hooks_test.go b/decode_hooks_test.go index 3ac00af9..367ed67a 100644 --- a/decode_hooks_test.go +++ b/decode_hooks_test.go @@ -7,6 +7,7 @@ import ( "math" "math/big" "net" + "net/mail" "net/netip" "net/url" "reflect" @@ -2123,3 +2124,36 @@ func TestErrorLeakageDecodeHook(t *testing.T) { } } } + +func TestStringToMailAddressHookFunc(t *testing.T) { + strValue := reflect.ValueOf("bg@example.com") + mailAddressValue := reflect.ValueOf(mail.Address{}) + + cases := []struct { + f, t reflect.Value + result any + err bool + }{ + {strValue, mailAddressValue, mail.Address{Address: "bg@example.com"}, false}, + {strValue, strValue, "bg@example.com", false}, + {reflect.ValueOf(strings.Repeat("bg@example.com", 3)), mailAddressValue, mail.Address{}, true}, + {reflect.ValueOf("Barry Gibbs "), mailAddressValue, mail.Address{Name: "Barry Gibbs", Address: "bg@example.com"}, false}, + {reflect.ValueOf("Barry Gibbs"), mailAddressValue, mail.Address{}, true}, + {reflect.ValueOf("Barry Gibbs <>"), mailAddressValue, mail.Address{}, true}, + {reflect.ValueOf("Barry Gibbs "), mailAddressValue, mail.Address{}, true}, + {reflect.ValueOf("Barry Gibbs "), mailAddressValue, mail.Address{Name: "Barry Gibbs", Address: "a-very-loooooooooooooongggggg-addresssssss@example.com"}, false}, + } + + for i, tc := range cases { + f := StringToMailAddressHookFunc() + actual, err := DecodeHookExec(f, tc.f, tc.t) + if tc.err != (err != nil) { + t.Fatalf("case %d: expected err %#v", i, err) + } + if !tc.err && !reflect.DeepEqual(actual, tc.result) { + t.Fatalf( + "case %d: expected %#v, got %#v", + i, tc.result, actual) + } + } +} From 0c76afb382a06a2821e172c4ee337a94f111a68b Mon Sep 17 00:00:00 2001 From: KianYang-Lee Date: Wed, 16 Jul 2025 10:49:57 +0800 Subject: [PATCH 4/5] rewrite tests --- decode_hooks_test.go | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/decode_hooks_test.go b/decode_hooks_test.go index 367ed67a..593a9f33 100644 --- a/decode_hooks_test.go +++ b/decode_hooks_test.go @@ -2126,34 +2126,20 @@ func TestErrorLeakageDecodeHook(t *testing.T) { } func TestStringToMailAddressHookFunc(t *testing.T) { - strValue := reflect.ValueOf("bg@example.com") - mailAddressValue := reflect.ValueOf(mail.Address{}) - - cases := []struct { - f, t reflect.Value - result any - err bool - }{ - {strValue, mailAddressValue, mail.Address{Address: "bg@example.com"}, false}, - {strValue, strValue, "bg@example.com", false}, - {reflect.ValueOf(strings.Repeat("bg@example.com", 3)), mailAddressValue, mail.Address{}, true}, - {reflect.ValueOf("Barry Gibbs "), mailAddressValue, mail.Address{Name: "Barry Gibbs", Address: "bg@example.com"}, false}, - {reflect.ValueOf("Barry Gibbs"), mailAddressValue, mail.Address{}, true}, - {reflect.ValueOf("Barry Gibbs <>"), mailAddressValue, mail.Address{}, true}, - {reflect.ValueOf("Barry Gibbs "), mailAddressValue, mail.Address{}, true}, - {reflect.ValueOf("Barry Gibbs "), mailAddressValue, mail.Address{Name: "Barry Gibbs", Address: "a-very-loooooooooooooongggggg-addresssssss@example.com"}, false}, + suite := decodeHookTestSuite[string, mail.Address]{ + fn: StringToMailAddressHookFunc(), + ok: []decodeHookTestCase[string, mail.Address]{ + {"Barry Gibbs ", mail.Address{Name: "Barry Gibbs", Address: "a-very-loooooooooooooongggggg-addresssssss@example.com"}}, + {"Barry Gibbs ", mail.Address{Name: "Barry Gibbs", Address: "bg@example.com"}}, + {"bg@example.com", mail.Address{Address: "bg@example.com"}}, + }, + fail: []decodeHookFailureTestCase[string, mail.Address]{ + {"Barry Gibbs"}, // no email + {"Barry Gibbs <>"}, // no email + {"Barry Gibbs "}, // invalid email + {"Barry Gibbs bg@example.com"}, // missing angle + }, } - for i, tc := range cases { - f := StringToMailAddressHookFunc() - actual, err := DecodeHookExec(f, tc.f, tc.t) - if tc.err != (err != nil) { - t.Fatalf("case %d: expected err %#v", i, err) - } - if !tc.err && !reflect.DeepEqual(actual, tc.result) { - t.Fatalf( - "case %d: expected %#v, got %#v", - i, tc.result, actual) - } - } + suite.Run(t) } From d279398d3b7845eb16cea57904d65feb6e6dfd95 Mon Sep 17 00:00:00 2001 From: KianYang-Lee Date: Wed, 16 Jul 2025 10:52:21 +0800 Subject: [PATCH 5/5] fix linter complaints --- decode_hooks.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/decode_hooks.go b/decode_hooks.go index 90c1b45b..da48f88f 100644 --- a/decode_hooks.go +++ b/decode_hooks.go @@ -717,11 +717,8 @@ func StringToComplex128HookFunc() DecodeHookFunc { // StringToMailAddressHookFunc returns a DecodeHookFunc that converts // strings to mail.Address. func StringToMailAddressHookFunc() DecodeHookFunc { - return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { - if f.Kind() != reflect.String { - return data, nil - } - if t != reflect.TypeOf(mail.Address{}) { + return func(f reflect.Type, t reflect.Type, data any) (any, error) { + if f.Kind() != reflect.String || t != reflect.TypeOf(mail.Address{}) { return data, nil }