gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
78.27k stars 7.99k forks source link

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

Open zhaojinxin409 opened 2 years ago

zhaojinxin409 commented 2 years ago

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 commented 2 years ago
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?

Bisstocuz commented 2 years ago
// 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 commented 2 years ago

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 commented 2 years ago

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

zhaojinxin409 commented 2 years ago

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 commented 2 years ago

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

ggicci commented 2 years ago

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

hongnguyenhuu96 commented 7 months ago

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
    }
weiyinfu commented 3 months ago

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