combining GO GIN-GONIC GORM and VALIDATOR.V2 - validation

I am quite new to Go and I would like to startup by setting a GIN-GONIC API. I found this tutorial and I am very happy with that skeleton. But now I am stuck with the validating process which I added: "gopkg.in/validator.v2" and
type Todo struct {
gorm.Model
Title string `json:"title"`
Completed int `json:"completed"`
}
became
type Todo struct {
gorm.Model
Title string `json:"title" **validate:"size:2"**`
Completed int `json:"completed"`
}
and then in the CreateTodo function which I added :
if errs := validator.Validate(todo); errs!=nil {
c.JSON(500, gin.H{"Error": errs.Error()})
}
but then a POST call send :
"Error": "Type: unknown tag"
after some research I found that :
Using a non-existing validation func in a field tag will always return false and with error validate.ErrUnknownTag.
so the **validate:"size:2"** must be wrong ...
I don't get how to set the validation and also how to display the correct error within the "catch":
c.JSON(500, gin.H{"Error": errs.Error()})

Looks like you haven't defined size validation function. Also you can do it.
Custom validation functions:
func size(v interface{}, param int) error {
st := reflect.ValueOf(v)
if st.Kind() != reflect.String {
return validate.ErrUnsupported
}
if utf8.RuneCountInString(st.String()) != param {
return errors.New("Wrong size")
}
return nil
}
validate.SetValidationFunc("size", size)

Related

gorm one to many relation

I have entities as bellow:
type Data struct {
Receiver string `json:"receiver"`
Status string `json:"status"`
Alerts alerts `json:"alerts" gorm:"foreignKey"`
}
type Alert struct {
Status string `json:"status"`
}
type alerts []Alert
func (sla *alerts) Scan(src interface{}) error {
return json.Unmarshal(src.([]byte), &sla)
}
func (sla alerts) Value() (driver.Value, error) {
val, err := json.Marshal(sla)
return string(val), err
}
and the migration is as bellow: db.AutoMigrate(apiV1.Data{}, apiV1.Alert{})
but when I try to put some value to data table. but it act as a text with Alerts.
so I can't query on alerts. also alert table is empty.

Using validator to check if value is boolean

I'm new to Go, so this might be very easy, but I can't find it. I have an entity Page with these two properties:
type Page struct {
Title string `form:"title" binding:"required"`
Active bool
}
Now, if you don't send a title we get a (not very pretty, but acceptable*):
Key: 'Page.Title' Error:Field validation for 'Title' failed on the 'required' tag.
Now, if I send this to the endpoint:
{
"title": "I'm a valid title",
"active": "I'm not a boolean at all!"
}
We get this:
json: cannot unmarshal string into Go struct field Page.Active of type bool
Which, IMO, is giving way too much info. What is the standard practice in Go to validate user input?
I was first making a page-validor.go with some checks, then I found this, but I'm not sure what is good practice in Go.
How do I validate this properly? Should I find check what is provided and then try to move it into the struct and validate the actual contents?
I am using GinGonic
* I've found a way to unwrap the errors and make it nicer
Write custom JSON Unmarshaller method for the type Page and inside UnmarshalJSON(bytes []byte) method, you can unmarshal the JSON bytes to map[string]interface{} and then validate the types you need with the JSON field keys.
An example of the JSON Unmarshaller looks like below.
type Page struct {
Title string `form:"title" binding:"required"`
Active bool
}
func (p *Page) UnmarshalJSON(bytes []byte) error {
var data map[string]interface{}
err := json.Unmarshal(bytes, &data)
if err != nil {
return err
}
actv, _ := data["active"]
if reflect.TypeOf(actv).Kind() != reflect.Bool {
return errors.New("active field should be a boolean")
}
p.Active = actv.(bool)
return nil
}
See the full example here in Playground.
After some more research, I've implemented Go-map-schema.
var page Page
src := make(map[string]interface{})
json.Unmarshal(jsonData, &src)
results, _ := schema.CompareMapToStruct(page, src, nil)
fmt.Println(results.MissingFields)
fmt.Println(results.MismatchedFields)
This works simple with the standard notations for an struct:
type Page struct {
Title string `json:"title" validator:"required"`
Active bool `json:"metaRobotsFollow" validate:"required,bool"`
}
You should use validator v10 available with Go
validator documentation go
For your use case you can use boolean validator
https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Boolean
type Page struct {
Title string `json:"title" binding:"required"`
Active bool `json:"active" binding:"required,boolean"`
}
Below is sample Test case with one positive and negative
func TestPage(t *testing.T) {
tests := []struct {
testName string
input string
wantErr bool
}{
{
testName: "positive test",
input: `{
"title": "first book title",
"active": false
}`,
wantErr: false,
}, {
testName: "wrong boolean",
input: `{
"title": "second book title",
"active": falsee
}`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
var p Page
b := []byte(tt.input)
err := json.Unmarshal(b, &p)
assert.Nil(t, err, "got error %v", err)
})
}

Go-Gin binding data with one-to-many relationship

I'm new to Golang and Gin framework, I have created two models
type Product struct {
gorm.Model
Name string
Media []Media
}
type Media struct {
gorm.Model
URI string
ProductID uint
}
and I send a POST request to save a new product, the body was:
{
"name": "Product1",
"media": [
"https://server.com/image1",
"https://server.com/image2",
"https://server.com/image3",
"https://server.com/video1",
"https://server.com/video2"
]
}
And I save a new product using this code
product := Product{}
if err := context.ShouldBindJSON(product); err != nil { // <-- here the error
context.String(http.StatusBadRequest, fmt.Sprintf("err: %s", err.Error()))
return
}
tx := DB.Create(&product)
if tx.Error != nil {
context.String(http.StatusBadRequest, fmt.Sprintf("err: %s", tx.Error))
return
}
the return error message is
err: json: cannot unmarshal string into Go struct field Product.Media of type models.Media
I know that ShouldBindJSON can't convert media-string to media-object, but what is the best practice to do this?
Your payload doesn't match the model. In the JSON body, media is an array of strings, whereas in the model it's a struct with two fields and the embedded gorm model.
If you can't change anything of your current setup, implement UnmarshalJSON on Media and set the URI field from the raw bytes. In the same method you may also initialize ProductID to something (if needed).
func (m *Media) UnmarshalJSON(b []byte) error {
m.URI = string(b)
return nil
}
Then the binding will work as expected:
product := Product{}
// pass a pointer to product
if err := context.ShouldBindJSON(&product); err != nil {
// handle err ...
return
}
fmt.Println(product) // {Product1 [{"https://server.com/image1" 0} ... }

How can I validate a struct datatype with a custom validator?

I am using go-playground/validator/v10 to validate some input and have some trouble with custom validation tags and functions. The problem is that the function is not called when one of the struct fields is a different struct. This is an example:
type ChildStruct struct {
Value int
}
type ParentStruct struct {
Child ChildStruct `validate:"myValidate"`
}
func myValidate(fl validator.FieldLevel) bool {
fmt.Println("INSIDE MY VALIDATOR") // <- This is never printed
return false
}
func main() {
validator := validator.New()
validator.RegisterValidation("myValidate", myValidate)
data := &ParentStruct{
Child: ChildStruct{
Value: 10,
},
}
validateErr := validator.Struct(data)
if validateErr != nil { // <- This is always nil since MyValidate is never called
fmt.Println("GOT ERROR")
fmt.Println(validateErr)
}
fmt.Println("DONE")
}
If I change the parentStruct to:
type ParentStruct struct {
Child int `validate:"myValidate"`
}
everything works.
If I add the validate:"myValidate" part to the ChildStruct it is also working, however, then the error that is returned is saying that the ChildStruct.Value is wrong when it should say that the ParentStruct.Child is wrong.
Anyone know what I am doing wrong?
After searching for a while I finally found a function called RegisterCustomTypeFunc which registers a custom type which will make it possible for go-playground/validator/v10 to validate it. So the solution is to add the following to the example in the question:
func childStructCustomTypeFunc(field reflect.Value) interface{} {
if value, ok := field.Interface().(ChildStruct); ok {
return value.Value
}
return nil
}
Together with:
validator.RegisterCustomTypeFunc(childStructCustomTypeFunc, ChildStruct{})
Now the validator will go into the myValidate function and the return message will be an error for the ParentStruct.Child field
The function validator.RegisterValidation(...) is registering a field level custom validator, as the type of the registered function suggests func(fl validator.FieldLevel) bool.
Struct fields themselves are not validated this way and your custom validator is ignored.
To validate a struct field you should use validate.RegisterStructValidation(myValidate, ChildStruct{}), where the function myValidate is of type validator.StructLevelFunc.
Inside this function you can then perform validation of the struct, either the field itself and/or its nested fields:
func myValidate(sl validator.StructLevel) {
fmt.Println("INSIDE MY VALIDATOR") // now called
if sl.Current().Interface().(ChildStruct).Value != 20 {
sl.ReportError(sl.Current().Interface(), "ChildStruct", "", "", "")
}
}
func main() {
vald := validator.New()
vald.RegisterStructValidation(myValidate, ChildStruct{})
data := &ParentStruct{
Child: ChildStruct{
Value: 10,
},
}
validateErr := vald.Struct(data)
if validateErr != nil {
fmt.Println("GOT ERROR")
fmt.Println(validateErr)
}
fmt.Println("DONE")
}
Example on the playground: https://play.golang.org/p/f0f2YE_e1VL

Detecting failed struct field - govalidator

I'm experimenting with govalidator - https://github.com/asaskevich/govalidator
I would like to know whether it's possible to detect which field in a struct has failed a validation check so that I can return an appropriate error message. So for example:
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"required"`
}
result, err := govalidator.ValidateStruct(post)
if err != nil {
//if title is missing then show error 1
//if message is missing then show error 2
}
This seems to be similar to issue/67:
At this moment it gives error like this:
Title: My123 does not validate as alpha;
AuthorIP: 123 does not validate as ipv4;
I create function ErroByField(e error, field string) that will return error for specified field of struct or empty string otherwise, I hope that it will be helpful.
For example:
type Post struct {
Title string `valid:"alpha,required"`
Message string `valid:"ascii"`
AuthorIP string `valid:"ipv4"`
}
post := &Post{"My123", "duck13126", "123"}
result, err := govalidator.ValidateStruct(post)
titleError := govalidator.ErrorByField(err, "Title")
if titleError != "" {
println(titleError) // -> My123 does not validate as alpha
}

Resources