gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
79.03k stars 8.03k forks source link

validate tag not working in ShouldBindWith #3234

Closed rahmathu closed 2 years ago

rahmathu commented 2 years ago

Description

I am trying to get the validation working in gin, with the validator package this validator tag works good. When i use it in gin it's not working and i am not sure if i am using it wrong or it's not working as a bug. What all i did before opening this issue

  1. Took just the validator lib and did a small test case to make sure validator and tags are good
  2. Google or issue search does not show this validate tag being used and errors on it
  3. The looked at gin code to see that validation is triggered but not working in my case

I looked at the gin code and see here that ShouldBindWith() should eventually call this . https://github.com/gin-gonic/gin/blob/4b68a5f12af4d6d2be83e1895f783d5dd5d5a148/binding/query.go Once validate() is called it should validate using the validator package for the validate tag, which is not working as expected

validate:"omitempty,oneof=ASC DESC

Any help is appreciated on this

How to reproduce

package main

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
)

// MyStruct ..
type MyStruct struct {
    Order string `form:"order,default=DESC" validate:"omitempty,oneof=ASC DESC"`
}

func startPage(c *gin.Context) {
    var person MyStruct
    if c.ShouldBindWith(&person, binding.Query) == nil {
        log.Println("====== Only Bind By Query String ======")
        log.Println(person.Order)
        c.String(http.StatusOK, "Success")
        return
        // log.Println(person.Address)
    }
    c.String(http.StatusBadRequest, "Failed")
    return
}

func main() {

    route := gin.Default()
    route.Any("/testing", startPage)
    route.Run(":8085")

        // This is just to show you guys that validate package works well throwing errors 
    // validate = validator.New()
    // validate.RegisterValidation("method", ValidateMyVal)

    // s := MyStruct{Order: "awesome"}

    // err := validate.Struct(s)
    // if err != nil {
    //  fmt.Printf("Err(s):\n%+v\n", err)
    // } else {
    //  fmt.Println("awesome validation looks good ")
    // }

    // s.Order = "not awesome"
    // err = validate.Struct(s)
    // if err != nil {
    //  fmt.Printf("Err(s):\n%+v\n", err)
    // }

    // s.Order = "awesome mess"
    // err = validate.Struct(s)
    // if err != nil {
    //  fmt.Printf("Err(s):\n%+v\n", err)
    // } else {
    //  fmt.Println("mess validation looks good ")
    // }

}

Expectations

Here i expect the curl command to return me a StatusBadRequest and text "failed"

$ curl -G -d 'order=WRONG'  http://localhost:8085/testing
Failed

Actual result

Actual result is code StatusOk and "Success"

√ go_learning/learngo % curl -G -d 'order=WRONG'  http://localhost:8085/testing
Success         

Environment

eleven26 commented 2 years ago

You should use binding, not validate:

binding:"omitempty,oneof=ASC DESC"

Because gin has modified the validator's tagname : default_validator.go#L95

func (v *defaultValidator) lazyinit() {
    v.once.Do(func() {
        v.validate = validator.New()
        v.validate.SetTagName("binding")
    })
}
rahmathu commented 2 years ago

@eleven26 : Thanks for the replies , some more questions to this

If i use binding in place of validate the "omitempty" does not work as it tries to unmarshall and expects it to be there. Do you know how may i do an omitempty and then oneof in the same tag. In case it will be a custom validator please give me an example with this sample code as i have tried may options but it's not wokring for me . Thanks in advance

eleven26 commented 2 years ago

The omitempty in binding just means that if your request does not have this field in it, it will not check other validation rules.

rahmathu commented 2 years ago

@eleven26 : Thanks for the confirmation, let me try out and get back. Till then please leave the issue open, i expect to close on this by this Friday . I will close the issue with comments my self if all goes well

rahmathu commented 2 years ago

@eleven26 : Thanks all the things worked except on last thing on the validator for dates

type GeneralValidatorQueryParams struct {
    StartedAt time.Time `form:"startedAt" binding:"omitempty,required_with=EndedAt"`
    EndedAt   time.Time `form:"endedAt" binding:"omitempty,required_with=StartedAt,gtfield=StartedAt"`
}

In this case i was trying to use this for a GET request and these are query parameters.

  1. When i put query parameter as in the URL "/testGeneralInputValidator?endedAt=2009-01-06T12:59:59Z". I see that the validator is not showing an error when i have missed the startedAt field.
  2. I have also tried the reverse for "/testGeneralInputValidator?startedAt=2009-01-10T12:59:59Z" . I see that the validation does not fail for this query parameter. I am not sure why the tag required_with is not taking effect here with binding .
eleven26 commented 2 years ago
  1. You should not use omitempty, because you already have required_with, which means you only check StartedAt when you pass EndedAt parameter, otherwise, validator will not check other validation rules when it sees omitempty:
type GeneralValidatorQueryParams struct {
    StartedAt time.Time `form:"startedAt" binding:"required_with=EndedAt"`
    EndedAt   time.Time `form:"endedAt" binding:"omitempty,required_with=StartedAt,gtfield=StartedAt"`
}

This may be a correct example.

  1. Same as before. If you use required_with you can't use omitempty anymore, if you add omitempty and the field doesn't exist required_with and other validation rules no longer work

You may not fully understand the usage of omitempty. The following is the description of omitempty in the official documentation:

validator.v9#hdr-Omit_Empty Allows conditional validation, for example if a field is not set with a value (Determined by the "required" validator) then other validation such as min or max won't run, but if a value is set validation will run.

rahmathu commented 2 years ago

got it thanks, i will close the issue now :)