gin icon indicating copy to clipboard operation
gin copied to clipboard

BindQuery to custom type

Open edebernis opened this issue 4 years ago • 13 comments

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

edebernis avatar Feb 04 '21 15:02 edebernis

I think you can bind it using a binding like: binding="uuid", check full docs here: validator

tprei avatar Feb 04 '21 16:02 tprei

Hello,

Thanks for your help, however same error with:

type QueryStruct struct {
	MyID ID `form:"id" binding:"uuid"`
}

edebernis avatar Feb 04 '21 16:02 edebernis

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

tprei avatar Feb 04 '21 16:02 tprei

Same error with all versions listed.

edebernis avatar Feb 04 '21 17:02 edebernis

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 }

`

zadil avatar Mar 01 '21 09:03 zadil

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.

edebernis avatar Mar 01 '21 13:03 edebernis

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

radhian-amri avatar Mar 18 '21 05:03 radhian-amri

Related issue: https://github.com/gin-gonic/gin/issues/2673

kszafran avatar Aug 06 '21 16:08 kszafran

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 avatar Jan 20 '22 19:01 ssoroka

@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

kszafran avatar Jan 20 '22 19:01 kszafran

that's fine with me, and would solve my problem. At the moment there is no way to do this.

ssoroka avatar Jan 20 '22 19:01 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))
	}

ssoroka avatar Jan 20 '22 20:01 ssoroka

This is still an issue. When will it be fixed in gin?

eloyekunle avatar Apr 06 '23 00:04 eloyekunle