diff --git a/decode_hooks.go b/decode_hooks.go index a852a0a0..da48f88f 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,25 @@ 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 any) (any, error) { + if f.Kind() != reflect.String || 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 + } +} diff --git a/decode_hooks_test.go b/decode_hooks_test.go index 3ac00af9..593a9f33 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,22 @@ func TestErrorLeakageDecodeHook(t *testing.T) { } } } + +func TestStringToMailAddressHookFunc(t *testing.T) { + 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 + }, + } + + suite.Run(t) +}