How to parse string to mail.Address
I am trying to parse the following
type NewEmail struct {
From mail.Address `form:"from"`
// ...
}
with
// In handler
if err := c.Bind(dtoPtr); err != nil { // dtoPtr is *NewEmail
errResponse = c.JSON(http.StatusBadRequest, Res(ResData{
Msg: "Review your input",
D: err, // here's the error
}))
return
}
Getting following error unknown type
{
"d": {
"message": "unknown type"
},
"msg": "Review your input",
"status": false
}
I've checked that it works if I make NewEmail structs From field to string from mail.Address. It is beneficial for me if I can parse to mail.Address based on my use case. How this can be done?
Could you provide how mail.Address is declared? Is is struct or alias or ?
The type mail.Address is from net/mail of StdLib
That struct looks like the following
type Address struct {
Name string
Address string
}
Echo version github.com/labstack/echo/v4 v4.13.3
This
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"log/slog"
"net/http"
"net/mail"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.POST("/", func(c echo.Context) error {
type NewEmail struct {
From mail.Address `form:"from"`
}
dto := &NewEmail{}
if err := c.Bind(dto); err != nil { // dto is *NewEmail
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
}
return c.JSON(http.StatusOK, dto)
})
if err := e.Start(":8080"); err != nil {
slog.Error("failed to start server")
}
}
does not work because you can only bind forms to (struct) fields that are explicitly marked with struct tags. But currently the target is struct with additional fields - this is not supported.
You could create your own address type and add tags
e.POST("/", func(c echo.Context) error {
type Address struct {
Name string `form:"name"`
Address string `form:"address"`
}
type NewEmail struct {
From Address
}
dto := &NewEmail{}
if err := c.Bind(dto); err != nil { // dto is *NewEmail
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
}
return c.JSON(http.StatusOK, dto)
})
test with
curl --form name=testName --form address=testAddress http://localhost:8080/
note:
curl -H "Content-Type: application/json" --data '{"from":{"name":"test","address":"address"}}' http://localhost:8080/ would work with
e.POST("/", func(c echo.Context) error {
type NewEmail struct {
From mail.Address
}
dto := &NewEmail{}
if err := c.Bind(dto); err != nil { // dto is *NewEmail
return c.JSON(http.StatusBadRequest, map[string]interface{}{"error": err.Error()})
}
return c.JSON(http.StatusOK, dto)
})
but this is because standard library JSON unmarshalling supports this
I know that this issue is old, but I'm writing this here just in case it could help someone else with a similar parsing issue, either for mail.Address or for any other struct.
I believe that what @apuatcfbd wants to do is parse the mail.Address from an RFC 5322 email address string, similar to the behavior of the mail.ParseAddress function. An RFC 5322 email address string is of the format:
John Doe <[email protected]>
which would then be parsed by mail.ParseAddress as:
mail.Address{Name: "John Doe", Address: "[email protected]"}
By default, Echo's binding functionality does not support the mail.Address struct. However, Echo bindings do support types that implement the interface encoding.TextUnmarshaler (see binder.go). So we can make our own email address struct that implements TextUnmarshaler as follows:
type mailAddressUnmarshalable struct {
mail.Address
}
// Implement encoding.TextUnmarshaler
func (m *mailAddressUnmarshalable) UnmarshalText(text []byte) error {
add, err := mail.ParseAddress(string(text))
m.Address = *add
return err
}
and update our NewEmail struct as follows:
type NewEmail struct {
From mailAddressUnmarshalable `form:"from"`
}
Now the binding occurs properly, there is no error, and the struct field is filled with the correct value.