- With issues:
- Use the search tool before opening a new issue.
- Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them.
Description
I have to place a request in a struct which uses fields from both headers and json body. Is there a way to bind to a struct with json and header struct tags both ? Like a ShouldBindWith which takes Json and header binding both ?
How to reproduce
Code :
package main
import (
"github.com/gin-gonic/gin"
)
type Request struct {
AppCode string `header:"appCode" binding:"required"`
SomeId string `json:"someId" binding:"required"`
UserId string `header:"userId"`
EmailId string `json:"emailId"`
}
func bindTest(ctx *gin.Context) {
var request Request
err = ctx.ShouldBindJSON(&request)
if err != nil {
err = errors.REQUEST_BIND_FAIL(err, request)
return
}
err = ctx.ShouldBindHeader(&request)
if err != nil {
err = errors.REQUEST_BIND_FAIL(err, request)
return
}
}
Expectations
- A common function which does both. or
- When binding required with JSON, it checks only the json struct tags and binding required with Headers only check the header struct tags.
Actual result
Currently, Im getting a binding:required error from a field which only has a header tag and not json tag when I do ShouldBindJSON.
Comment From: linvis
type Request struct {
AppCode string `header:"appCode" json:"-" `
SomeID string `header:"-" json:"someId"`
UserID string `header:"userId" json:"-"`
EmailID string `header:"-" json:"emailId"`
}
just use "-" to remove tag that you don't want.
Comment From: linvis
and binding:"required
should not be used if you want to ignore one tag
Comment From: o10g
and
binding:"required
should not be used if you want to ignore one tag
what if I would like to use validation? OP asks how to combine headers and json/form values in one struct. As far as I see it is not possible right now.
Comment From: ghost
I guess you could create an extra function like:
type Request struct {
AppCode string `header:"appCode" json:"-" `
SomeID *string `header:"-" json:"someId"`
UserID string `header:"userId" json:"-"`
EmailID string `header:"-" json:"emailId"`
}
func (r *Request) Validate(context *gin.Context) bool {
if r.SomeID == nil {
// do something with context
context.abort()
return false
}
return true
}
maibe it's not ideal, but i've been working this way for some specific stuff and all went good so far
Comment From: Emixam23
Hey! I've got the same issue..
func (api *api) UpdateStuff(c *gin.Context) {
type updateRequest struct {
User string `form:"user" binding:"required,oneof=foo bar doe"`
UserAgent string `header:"User-Agent" binding:"required"`
}
var update updateRequest
if err := c.BindQuery(&update); err != nil {
_ = c.Error(err)
c.JSON(http.StatusBadRequest, errorContainer{Error: err.Error()})
return
} else if err := c.BindHeader(&update); err != nil {
_ = c.Error(err)
c.JSON(http.StatusBadRequest, errorContainer{Error: err.Error()})
return
}
// ...
c.JSON(http.StatusOK, updateResponse{
Count: count,
})
}
and I always get
{"error":"Key: 'updateRequest.UserAgent' Error:Field validation for 'UserAgent' failed on the 'required' tag"}
I tried without the required field but.. Doesn't help as I wished
Thanks
Comment From: Parsa-Sedigh
I think it's not possible to bind a struct which has both json
and header
(or other tags like uri
). Because gin can bind one of them at a time. You can bind header and json fields in two steps like:
type RequestHeaders struct {
AppCode string `header:"appCode" binding:"required"`
UserId string `header:"userId"`
}
type RequestBody struct {
SomeId string `json:"someId" binding:"required"`
EmailId string `json:"emailId"`
}
func bindTest(ctx *gin.Context) {
var body RequestBody
var headers RequestHeaders
err = ctx.ShouldBindJSON(&body)
if err != nil {
err = errors.REQUEST_BIND_FAIL(err, request)
return
}
err = ctx.ShouldBindHeader(&headers)
if err != nil {
err = errors.REQUEST_BIND_FAIL(err, request)
return
}
}
Comment From: zaydek
I got curious about this and I like this solution because it keeps everything together but it respects that headers and body needs to be bound separately.
package main
import (
"bytes"
"net/http"
"github.com/gin-gonic/gin"
"github.com/k0kubun/pp"
)
type Request struct {
Headers struct {
UserID int `header:"user-id" json:"-" binding:"required"`
Authorization string `header:"Authorization" json:"-" binding:"required"`
}
Body struct {
Body string `json:"body" binding:"required"`
}
}
func main() {
router := gin.Default()
router.POST("/", func(c *gin.Context) {
// Bind the request
var req Request
if err := c.ShouldBindHeader(&req.Headers); err != nil {
panic(err)
}
if err := c.ShouldBindJSON(&req.Body); err != nil {
panic(err)
}
// Debug print the request
pp.Println(req)
})
go func() {
body := []byte(`{"body":"foo"}`)
req, err := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(body))
if err != nil {
panic(err)
}
req.Header.Set("user-id", "123")
req.Header.Set("Authorization", "Bearer AccessToken")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}()
router.Run(":8080")
}
Here's what the output looks like.
Comment From: zaydek
Nevermind that's kind of dumb just do this lol
func (r *Router) AuthLogoutDevice(c *gin.Context) {
type Request struct {
UserID string `json:"-"`
AuthorizationToken string `json:"-"`
DeviceID string `json:"deviceID"`
}
// Bind the request
var request Request
var err error
request.UserID = c.GetHeader("user-id")
request.AuthorizationToken, err = getAuthorizationTokenFromHeaders(c)
if err != nil {
r.debugError(c, http.StatusInternalServerError, fmt.Errorf("failed to get authorization token from headers: %w", err))
return
}
if err := c.ShouldBindJSON(&request); err != nil {
r.debugError(c, http.StatusInternalServerError, fmt.Errorf("failed to bind JSON: %w", err))
return
}
// TODO
}
Comment From: VILJkid
I got curious about this and I like this solution because it keeps everything together but it respects that headers and body needs to be bound separately.
```go package main
import ( "bytes" "net/http"
"github.com/gin-gonic/gin" "github.com/k0kubun/pp" )
type Request struct { Headers struct { UserID int
header:"user-id" json:"-" binding:"required"
Authorization stringheader:"Authorization" json:"-" binding:"required"
} Body struct { Body stringjson:"body" binding:"required"
} }func main() { router := gin.Default()
router.POST("/", func(c *gin.Context) { // Bind the request var req Request if err := c.ShouldBindHeader(&req.Headers); err != nil { panic(err) } if err := c.ShouldBindJSON(&req.Body); err != nil { panic(err) }
// Debug print the request pp.Println(req)
})
go func() { body := []byte(
{"body":"foo"}
) req, err := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(body)) if err != nil { panic(err) } req.Header.Set("user-id", "123") req.Header.Set("Authorization", "Bearer AccessToken") req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() }()router.Run(":8080") } ```
Here's what the output looks like.
![]()
I think segregation is the key. I was refactoring my code and then found this. Will check and see how it works out. Thanks for the idea @zaydek!
Comment From: VILJkid
It worked perfectly @zaydek! 🥳 Populating values in a single struct made so many things simpler!
Comment From: appleboy
I have closed the issue. Please don't hesitate to reopen it if you encounter any more problems. Thanks @zaydek