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: