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

Input Validation Get JSON tags #46

Closed
hmajid2301 opened this issue Nov 20, 2020 · 1 comment
Closed

Input Validation Get JSON tags #46

hmajid2301 opened this issue Nov 20, 2020 · 1 comment

Comments

@hmajid2301
Copy link
Contributor

Hi I know you've already provided me with a lot of help.
I have another question, I know this might not be directly related to Fizz, however I cannot seem to
work out how to get it working with Fizz/Tonic. However others can get it work with Gin directly.

It is around input validation errors, I know validators is used to check the incoming data. So if I do

curl -X POST http://localhost:8080/game  -H "Content-Type: application/json" -d "{\"name\":\"quibly\",\"ruls_url\":\"https://gitlab.com/banter-bus/games\"}"

# Output
{
    "message": "binding error: Key: 'NewGame.RulesURL' Error:Field validation for 'RulesURL' failed on the 'required' tag"
}

Where the expected data is modelled like:

type NewGame struct {
	Name     string `json:"name"      validate:"required"`
	RulesURL string `json:"rules_url" validate:"required"`
}

I would like to use just get the json field names i.e. rules_url and create my own error message to return back to the client.

for _, err := range e.(tonic.BindError).ValidationErrors() {
	fmt.Println(err.Namespace())
	fmt.Println(err.Field())
	fmt.Println(err.StructNamespace())
	fmt.Println(err.StructField())
	fmt.Println(err.Tag())
	fmt.Println(err.ActualTag())
	fmt.Println(err.Kind())
	fmt.Println(err.Type())
	fmt.Println(err.Value())
	fmt.Println(err.Param())
	fmt.Println()
}

Which outputs:

NewGame.RulesURL
RulesURL
NewGame.RulesURL
RulesURL
required
required
string
string

I've tried to add a custom validator to gin as Validators maintainer suggests here

	binding.Validator = new(defaultValidator)
	
        engine := gin.New()
	engine.Use(cors.Default())
	fizzApp := fizz.NewFromEngine(engine)

       // ....
	infos := &openapi.Info{
		Title:       "Banter Bus",
		Description: "The API definition for the Banter Bus server.",
		Version:     "1.0.0",
	}

Where the defaultValidator looks like:

import (
	"reflect"
	"strings"
	"sync"

	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

type defaultValidator struct {
	once     sync.Once
	validate *validator.Validate
}

var _ binding.StructValidator = &defaultValidator{}

func (v *defaultValidator) ValidateStruct(obj interface{}) error {

	if kindOfData(obj) == reflect.Struct {

		v.lazyinit()

		if err := v.validate.Struct(obj); err != nil {
			return err
		}
	}

	return nil
}

func (v *defaultValidator) Engine() interface{} {
	v.lazyinit()
	return v.validate
}

func (v *defaultValidator) lazyinit() {
	v.once.Do(func() {
		v.validate = validator.New()
		v.validate.SetTagName("binding") // Print JSON name on validator.FieldError.Field()
		v.validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			return name
		})
	})
}

func kindOfData(data interface{}) reflect.Kind {

	value := reflect.ValueOf(data)
	valueType := value.Kind()

	if valueType == reflect.Ptr {
		valueType = value.Elem().Kind()
	}
	return valueType
}

I was wondering if you could suggest a way I could get the JSON tag (`rules_url) from the struct rather than the struct field name(RulesURL). I've tried debugging it myself with breakpoints but I cannot seem to work it out. Any help would be appreciated thanks.

P.S. Sorry for the information overload.

@hmajid2301
Copy link
Contributor Author

Ok further investigation this is an issue with tonic:

in the handler.go

If update initValidator() to

func initValidator() {
	validatorOnce.Do(func() {
		validatorObj = validator.New()
		validatorObj.SetTagName(ValidationTag)
		validatorObj.RegisterTagNameFunc(func(fld reflect.StructField) string {
			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
			if name == "-" {
				return ""
			}
			return name
		})
	})
}

fmt.Println(err.Field()) returns rules_url which is the JSON struct tag.

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

No branches or pull requests

1 participant