BindQuery to custom type
Description
I would like to bind a query parameter into a custom type (uuid.UUID). Is there an interface to implement on custom type to support this ?
It works with json binding for example, as custom type implements Unmarshaler interface.
How to reproduce
package main
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type ID uuid.UUID
type QueryStruct struct {
MyID ID `form:"id"`
}
func main() {
g := gin.Default()
g.GET("/test", func(c *gin.Context) {
var query QueryStruct
if err := c.BindQuery(&query); err != nil {
c.AbortWithError(400, err)
return
}
c.String(200, "OK")
})
g.Run(":9000")
}
Expectations
$ curl http://localhost:9000/test?id=46697e30-349e-4d21-b586-a18674e50385
OK
Actual result
$ curl http://localhost:9000/test?id=46697e30-349e-4d21-b586-a18674e50385
["46697e30-349e-4d21-b586-a18674e50385"] is not valid value for main.ID
Environment
- go version: 1.15.7
- gin version (or commit ref): 1.6.3
- operating system: darwin/amd64
I think you can bind it using a binding like: binding="uuid", check full docs here: validator
Hello,
Thanks for your help, however same error with:
type QueryStruct struct {
MyID ID `form:"id" binding:"uuid"`
}
In the google/uuid it says that their uuid's are based on RFC 4122, so maybe try binding="uuid_rfc4122". If that doesn't work, try other versions as listed in the docs: validator
Same error with all versions listed.
Hello, You can do otherwise like this :
uuid := c.Query("id") if !IsValidUUID(uuid) { err := errors.New("A simple message") c.AbortWithError(400, err) return }
` import uuid "github.com/satori/go.uuid"
func IsValidUUID(u string) bool { _, err := uuid.FromString(u) return err == nil }
`
Hello @zadil,
Thanks for your answer, however, the problem is not on validation. The issue is on binding, which interface can I implement on custom type "ID" to allow binding.
Hi @edebernis
Binding it to uuid.UUID is not possible since the datatype is array(type UUID [16]byte) and what you are providing is string. Thats why its getting the error ["46697e30-349e-4d21-b586-a18674e50385"] is not valid value for main.ID (notice it is in array format)
What you can do is create a separate field to parse the string value into the new field
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type QueryStruct struct {
MyID string `form:"id" json:"-"`
NewID uuid.UUID `json:"id"`
}
func main() {
g := gin.Default()
g.GET("/test", func(c *gin.Context) {
var query QueryStruct
if err := c.BindQuery(&query); err != nil {
c.AbortWithError(400, err)
return
}
query.NewID, _ = uuid.Parse(query.MyID)
c.JSON(200, query)
})
g.Run(":9000")
}
let me know if this helps
Related issue: https://github.com/gin-gonic/gin/issues/2673
I'm having the same problem. What we need is a way to support custom unmarshalling for non-struct types. If you look at the setWithProperType function, it only checks for marshalling support for struct types, not for others. This causes problems for all []byte, or say, an int field that is rendered in hex to the client, or a custom timestamp field that stores the underlying value as an int, etc.
a simple solution here might just be to first check to see if the destination type supports the unmarshaller interface. Something like:
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
if _, ok := value.Interface().(json.Unmarshaler); ok {
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
}
switch value.Kind() {
...
@ssoroka Two remarks:
- I think it should be checking for the
encoding.TextUnmarshalerinterface for non-struct types - instead of calling
json.Unmarshal, you could just call theUnmarshalText(orUnmarshalJSON) function directly
There are some other things to consider, too. Check out this discussion: https://github.com/gin-gonic/gin/issues/2673
that's fine with me, and would solve my problem. At the moment there is no way to do this.
Here's what I ended up with
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
if u, ok := value.Addr().Interface().(encoding.TextUnmarshaler); ok {
return u.UnmarshalText(bytesconv.StringToBytes(val))
}
This is still an issue. When will it be fixed in gin?