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
Comment From: tprei
I think you can bind it using a binding like: binding="uuid"
, check full docs here: validator
Comment From: edebernis
Hello,
Thanks for your help, however same error with:
type QueryStruct struct {
MyID ID `form:"id" binding:"uuid"`
}
Comment From: tprei
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
Comment From: edebernis
Same error with all versions listed.
Comment From: zadil
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 }
`
Comment From: edebernis
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.
Comment From: radhian-amri
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
```package main
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
**Comment From: kszafran**
Related issue: https://github.com/gin-gonic/gin/issues/2673
**Comment From: ssoroka**
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:
```go
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() {
...
Comment From: kszafran
@ssoroka Two remarks:
- I think it should be checking for the encoding.TextUnmarshaler
interface for non-struct types
- instead of calling json.Unmarshal
, you could just call the UnmarshalText
(or UnmarshalJSON
) function directly
There are some other things to consider, too. Check out this discussion: https://github.com/gin-gonic/gin/issues/2673
Comment From: ssoroka
that's fine with me, and would solve my problem. At the moment there is no way to do this.
Comment From: ssoroka
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))
}
Comment From: eloyekunle
This is still an issue. When will it be fixed in gin?