Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BindQuery to custom type #2631

Closed
edebernis opened this issue Feb 4, 2021 · 13 comments · Fixed by #3933
Closed

BindQuery to custom type #2631

edebernis opened this issue Feb 4, 2021 · 13 comments · Fixed by #3933

Comments

@edebernis
Copy link
Contributor

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
@tprei
Copy link

tprei commented Feb 4, 2021

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

@edebernis
Copy link
Contributor Author

Hello,

Thanks for your help, however same error with:

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

@tprei
Copy link

tprei commented Feb 4, 2021

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

@edebernis
Copy link
Contributor Author

Same error with all versions listed.

@zadil
Copy link

zadil commented Mar 1, 2021

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
}

`

@edebernis
Copy link
Contributor Author

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.

@radhian-amri
Copy link

radhian-amri commented Mar 18, 2021

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

@kszafran
Copy link
Contributor

kszafran commented Aug 6, 2021

Related issue: #2673

@ssoroka
Copy link

ssoroka commented Jan 20, 2022

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() {
	...

@kszafran
Copy link
Contributor

@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: #2673

@ssoroka
Copy link

ssoroka commented Jan 20, 2022

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

@ssoroka
Copy link

ssoroka commented Jan 20, 2022

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))
	}

@eloyekunle
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants