Description
I am trying to create a struct level validator that would modify the original struct. The reason for that is that I'm expecting the user to pass a hash and I need to convert this hash to a primary key (int). Without this ability, I would have to run the same query twice (first query inside the validator to validate the hash and then second query outside of the validator to get the actual PK). I have already found out the reason for my unexpected behavior and it is located in gin/binding/default_validator.go:
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
// (cut for brevity)
value := reflect.ValueOf(obj)
switch value.Kind() {
case reflect.Ptr:
return v.ValidateStruct(value.Elem().Interface())
so I believe what's happening is that my pointer gets de-referenced and even if I make changes inside the validator function they are lost.
I've thought of many ways to approach this but I can't figure out any other way (than passing a pointer). if I would use a field validator, it can only return a boolean, plus I don't have access to the struct so the StructLevel validation feels like a better way to accomplish this.
How to reproduce
package main
import (
"github.com/gin-gonic/gin"
)
type MyStruct struct {
PublicField string `binding:"required" json:"some_field"`
ProtectedField uint
}
func validateAndModifyStruct(sl validator.StructLevel) {
// the following code raises a 500 exception because sl.Top().Interface()
// is of type MyStruct, not *MyStruct (is de-referenced)
var ms *MyStruct = sl.Top().Interface().(*MyStruct)
ms.ProtectedField = 123
}
func main() {
g := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterStructValidation(validateAndModifyStruct, MyStruct{})
}
g.POST("/hello/", func(c *gin.Context) {
var ms MyStruct
c.ShouldBind(&ms)
c.JSON(200, ms)
})
g.Run(":9000")
}
Expectations
the expectation would be for the struct to be modified.
I am able to make this work on current gin code by converting c.ShouldBind into the following code (again, error handling cut out for brevity):
var ms MyStruct
decoder := json.NewDecoder(c.Request.Body)
decoder.Decode(&ms)
binding.Validator.Engine().(*validator.Validate).Struct(&ms)
it works as expected but it definetely feels hacky. What I would like to understand would be - why is gin behaving this way? It definetely feels intended. Is this for security reason?
Environment
- go version: 1.18
- gin version (or commit ref): 1.7.7
- operating system: