Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unable to pass a pointer to struct level validator (works with a workaround) #3143

Closed
toudi opened this issue May 5, 2022 · 0 comments · Fixed by #3199
Closed

unable to pass a pointer to struct level validator (works with a workaround) #3143

toudi opened this issue May 5, 2022 · 0 comments · Fixed by #3199

Comments

@toudi
Copy link

toudi commented May 5, 2022

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:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants
@toudi and others