gin icon indicating copy to clipboard operation
gin copied to clipboard

Can i bind uri and query to a struct in one method?

Open zhaojinxin409 opened this issue 3 years ago • 9 comments

Description

Can i bind uri and query in one method? Currently when i bind uri , it will return an error because of binding of query is empty.

Maybe we need a BindUrl function ?

How to reproduce

package main

import (
	"github.com/gin-gonic/gin"
	"log"
)

type DatasourceListRequest struct {
	Name  string `uri:"name" binding:"required"`
	Query string `form:"query" binding:"required""`
}

func main() {
	g := gin.Default()
	g.GET("/hello/:name", Handler)
	g.Run(":9000")
}

func Handler(ctx *gin.Context) {
	var req = new(DatasourceListRequest)
	if err := ctx.ShouldBindUri(req); err != nil {
		log.Println("bind uri error")
	}
	if err := ctx.ShouldBindQuery(req); err != nil {
		log.Println("bind query error")
	}
}

zhaojinxin409 avatar Oct 27 '21 03:10 zhaojinxin409

func Handler(ctx *gin.Context) {
	var req = new(DatasourceListRequest)
	if err := ctx.ShouldBindBodyWith(&req, binding.Uri); err != nil {
		log.Println("bind uri error")
	}
	if err := ctx.ShouldBindBodyWith(&req, binding.Query); err != nil {
		log.Println("bind query error")
	}
}

Sorry, i can't compile the code as the error is:

Cannot use 'binding.Uri' (type uriBinding) as the type binding.BindingBody Type does not implement 'binding.BindingBody' as some methods are missing: Bind(*http.Request, interface{}) error BindBody([]byte, interface{}) error

Besides, does the code above avoid print the bind uri error log?

zhaojinxin409 avatar Oct 27 '21 07:10 zhaojinxin409

// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
// body into the context, and reuse when it is called again.
//
// NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once.
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
	var body []byte
	if cb, ok := c.Get(BodyBytesKey); ok {
		if cbb, ok := cb.([]byte); ok {
			body = cbb
		}
	}
	if body == nil {
		body, err = ioutil.ReadAll(c.Request.Body)
		if err != nil {
			return err
		}
		c.Set(BodyBytesKey, body)
	}
	return bb.BindBody(body, obj)
}

This function will store the body at first calling, and it will get the store of body when you call it again.

Sorry for this error, I will try to research this issue.

Bisstocuz avatar Oct 27 '21 08:10 Bisstocuz

Maybe you are right,

Maybe we need a BindUrl function ?

Or we can simply let BindUri support query binding. Query field is empty when checking the struct after binding URI.

Bisstocuz avatar Oct 27 '21 09:10 Bisstocuz

There is a temporarily solution: Put field Name and Query into two different structs, you can bind them separately.

Bisstocuz avatar Oct 27 '21 09:10 Bisstocuz

Maybe you are right,

Maybe we need a BindUrl function ?

Or we can simply let BindUri support query binding. Query field is empty when checking the struct after binding URI.

BindUrl may be a better solution because of backwards compatibility :)

zhaojinxin409 avatar Oct 27 '21 09:10 zhaojinxin409

Maybe you are right,

Maybe we need a BindUrl function ?

Or we can simply let BindUri support query binding. Query field is empty when checking the struct after binding URI.

BindUrl may be a better solution because of backwards compatibility :)

Any idea about it? i can try to contribute if the idea is accepted

zhaojinxin409 avatar Nov 12 '21 03:11 zhaojinxin409

What about creating a middleware to achieve this? and considering a third-party package like ggicci/httpin

ggicci avatar Feb 22 '22 10:02 ggicci

I found that there is a method MapFormWithTag in the binding package for example if you want to skip binding query and only bind uri and validate after bind uri

        var req serializers.InternalGetMaxExpressCODAmountReq
	if err := binding.MapFormWithTag(&req, c.Request.URL.Query(), "form"); err != nil {
		log.Err(err).Ctx(c).Msg("Failed to parse request form")
		response.Error(c, http.StatusBadRequest, err, translate.ErrorBadRequest)
		return
	}

	if err := c.ShouldBindUri(&req); err != nil { // call bind only one time
		log.Err(err).Ctx(c).Msg("Failed to parse request uri")
		response.Error(c, http.StatusBadRequest, err, translate.ErrorBadRequest)
		return
	}

hongnguyenhuu96 avatar Feb 15 '24 10:02 hongnguyenhuu96

Now that gin doesn't support BindUrl(),we should think about other solutions.
Maybe we can define the struct better. The first method define a struct contains uri struct and query struct. The second method combound two struct.
I think it's clear and elegant. After all,we don't need to write annotations in one struct.

20240701-205742

weiyinfu avatar Jul 01 '24 13:07 weiyinfu