PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0
Showing posts with label go-gin. Show all posts
Showing posts with label go-gin. Show all posts

Monday, November 14, 2022

[FIXED] How to assert error type json.UnmarshalTypeError when caught by gin c.BindJSON

 November 14, 2022     error-handling, go, go-gin, json, validation     No comments   

Issue

I'm trying to catch binding errors with gin gonic and it's working fine for all validation errors from go-playground/validator/v10 but i'm having an issue catching errors when unmarshalling into the proper data type.

Unsuccessful validation of a struct field will return a gin.ErrorTypeBind Type of error when using validator tags ( required, ...)

but if i have a struct

type Foo struct {
  ID int `json:"id"`
  Bar string `json:"bar"`
}

And the json i'm trying to pass is of a wrong format (passing a string instead of a number for id )

{
    "id":"string",
    "bar":"foofofofo"
}

It will fail with an error json: cannot unmarshal string into Go struct field Foo.id of type int

It is still caught as a gin.ErrorTypeBind in my handler as an error in binding but as i need to differentiate between validation error and unmarshalling error i'm having issues.

I have tried Type casting on validaton error doesn't work for unmarshalling : e.Err.(validator.ValidationErrors) will panic

or just errors.Is but this will not catch the error at all

    if errors.Is(e.Err, &json.UnmarshalTypeError{}) {
        log.Println("Json binding error")
    } 

My goal in doing so is to return properly formatted error message to the user. It's currently working well for all the validation logic but i can't seem to make it work for json data where incorrect data would be sent to me.

any ideas?

edit :

adding example to reproduce :

package main

import (
    "encoding/json"
    "errors"
    "log"
    "net/http"

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

type Foo struct {
    ID  int    `json:"id" binding:"required"`
    Bar string `json:"bar"`
}

func FooEndpoint(c *gin.Context) {
    var fooJSON Foo
    err := c.BindJSON(&fooJSON)
    if err != nil {
        // caught and answer in the error MW
        return
    }
    c.JSON(200, "test")
}

func main() {
    api := gin.Default()
    api.Use(ErrorMW())
    api.POST("/foo", FooEndpoint)
    api.Run(":5000")
}

func ErrorMW() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        if len(c.Errors) > 0 {
            for _, e := range c.Errors {
                switch e.Type {
                case gin.ErrorTypeBind:
                    log.Println(e.Err)
                    var jsonErr json.UnmarshalTypeError
                    if errors.Is(e.Err, &jsonErr) {
                        log.Println("Json binding error")
                    }
                    // if errors.As(e.Err, &jsonErr) {
                    //  log.Println("Json binding error")
                    // }
                    // in reality i'm making it panic.
                    // errs := e.Err.(validator.ValidationErrors)
                    errs, ok := e.Err.(validator.ValidationErrors)
                    if ok {
                        log.Println("error trying to cast validation type")
                    }
                    log.Println(errs)
                    status := http.StatusBadRequest
                    if c.Writer.Status() != http.StatusOK {
                        status = c.Writer.Status()
                    }
                    c.JSON(status, gin.H{"error": "error"})
                default:
                    log.Println("other error")
                }

            }
            if !c.Writer.Written() {
                c.JSON(http.StatusInternalServerError, gin.H{"Error": "internal error"})
            }
        }
    }
}

trying sending a post request with a body

{
  "id":"rwerewr",
   "bar":"string"
}

interface conversion: error is *json.UnmarshalTypeError, not validator.ValidationErrors

this will work :

{
  "id":1,
   "bar":"string"
}

and this will (rightfully ) return Key: 'Foo.ID' Error:Field validation for 'ID' failed on the 'required' tag

{ "bar":"string" }


Solution

[update] : sice version 1.7.0, which integrates this PR, it is now possible to use errors.Is / errors.As on a gin.Error.

So you can write :

err := c.BindJson(&fooJson)

if err != nil {
var jsErr *json.UnmarshalTypeError
  if errors.As(err, &jsErr) {
    fmt.Println("the json is invalid")
  } else {
    fmt.Println("this is something else")
  }
}

[edit] : meh, it won't fix @Flimzy's answer : looking at the docs, gin.Error doesn't implement the .Unwrap() method.

You would have to first convert your error to a gin.Error, then check ginErr.Err :

// you can probably work with straight conversion from interface to target type :
if g, ok := err.(*gin.Error); ok {
    if _, ok := g.Err.(*json.UnmarshalTypeError); ok {
        log.Println("Json binding error")
    }
}

// or use errors.As() :
var g *gin.Error
if errors.As(err, &g) {
    var j *json.UnmarshalTypeError
    if errors.As(g.Err, &j) {
        log.Println("Json binding error")
    }
}

my initial answer :

(fixing @Fllimzy's answer)

  1. use errors.As
  2. since json.UnmarshalTypeError is a struct (not an interface), you have to explicitly disinguish between json.UnmarshalTypeError and *json.UnmarshalTypeError

Try running :

var jsonErr *json.UnmarshalTypeError  // emphasis on the '*'
if errors.As(e.Err, &jsonErr) {
    log.Println("Json binding error")
}

Here is an illustration of how errors.As() behaves :
https://play.golang.org/p/RVz6xop5k4u



Answered By - LeGEC
Answer Checked By - Candace Johnson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Wednesday, August 24, 2022

[FIXED] How to install Gin with Golang

 August 24, 2022     go, go-gin, linux, module, webserver     No comments   

Issue

I'm a newbie on Golang, and I'm trying to use Gin to develop a web server on Ubuntu 16.04.

After executing go get -u github.com/gin-gonic/gin, many folders appear at ~/go/pkg/mod/github.com/.

Then I try to make an example:

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

However, go run example.go made the error:

example.go:3:8: cannot find package "github.com/gin-gonic/gin" in any of:
        /usr/local/go/src/github.com/gin-gonic/gin (from $GOROOT)
        /home/zyh/go/src/github.com/gin-gonic/gin (from $GOPATH)

In my system, $GOROOT is /usr/local/go/ and $GOPATH is ~/go/.

How could I solve this problem?


Solution

For Go version 1.11 or newer, You should use Go Modules.

If you are just starting with Go, you should start with the newer version. I think you are using a Go version that supports go modules already because the modules you are trying to get are downloading to ~/go/pkg/mod/ directory.

To initialize a project with go module, run:

go mod init your-project-name

This will create a go.mod file in your project directory.

Add missing and/or remove unused modules:

go mod tidy

This will fill up the go.mod file with appropriate modules and create a go.sum in your project directory. The go.sum contains expected cryptographic hashes of each module version.

After that, the go run example.go command should run the program without any issues.


You can even vendor the modules in your project directory:

go mod vendor

This will bring all the vendors to your projects /vendor directory so that you don't need to get the modules again if working from another machine on this project.



Answered By - aniskhan001
Answer Checked By - Gilberto Lyons (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Saturday, July 30, 2022

[FIXED] How to validate headers and body in Gin-Gonic?

 July 30, 2022     go, go-gin, http, validation     No comments   

Issue

I have a Gin program. When a request comes, I want all of the fields of the variable data (type ProductCreate) to have values: UserId (from headers) and Name, Price (from JSON body). I used the below code and it works:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

type ProductCreate struct {
    UserId int    `header:"user-id"`
    Name   string `json:"name"`
    Price  int    `json:"price"`
}

func main() {
    r := gin.Default()

    r.POST("/product", func(c *gin.Context) {
        var data ProductCreate

        // bind the headers to data
        if err := c.ShouldBindHeader(&data); err != nil {
            c.JSON(400, err.Error())
            return
        }

        // bind the body to data
        if err := c.ShouldBindJSON(&data); err != nil {
            c.JSON(400, err.Error())
            return
        }

        c.JSON(200, data)
    })

    r.Run(":8080")
}

After that, I wanted to make sure that fields must be provided so I edited the ProductCreate struct like this:

type ProductCreate struct {
    UserId int    `header:"user-id" binding:"required"`
    Name   string `json:"name" binding:"required"`
    Price  int    `json:"price" binding:"required"`
}

Then it raised an unexpected error when I tested it again:

Key: 'ProductCreate.Name' Error:Field validation for 'Name' failed on the 'required' tag\nKey: 'ProductCreate.Price' Error:Field validation for 'Price' failed on the 'required' tag

I realized the error happened at this:

// bind the headers to data
if err := c.ShouldBindHeader(&data); err != nil {
   c.JSON(400, err.Error())
   return
}

Is there any solution to resolve my problem?


Solution

Can you try this?

curl --location --request POST 'http://localhost:8080/product' \
--header 'user-id: 20' \
--data-raw '{
    "name": "sr"   
}'

I tried your code and it works perfectly.

{
    "UserId": 20,
    "name": "sr",
    "price": 0
}

Gin version: github.com/gin-gonic/gin v1.8.1 // indirect

Soln:

package main

import (
    "github.com/gin-gonic/gin"
)

type ProductCreate struct {
    Name  *string `json:"name" binding:"required"`
    Price *int    `json:"price" binding:"required"`
}

type Header struct {
    UserId *int `header:"user-id" binding:"required"`
}

func main() {
    r := gin.Default()

    r.POST("/product", func(c *gin.Context) {
        data := &ProductCreate{}
        header := &Header{}

        // bind the headers to data
        if err := c.ShouldBindHeader(header); err != nil {
            c.JSON(400, err.Error())
            return
        }

        // bind the body to data
        if err := c.ShouldBindJSON(data); err != nil {
            c.JSON(400, err.Error())
            return
        }

        c.JSON(200, data)
    })

    r.Run(":8080")
}


Answered By - Srikanth Bhandary
Answer Checked By - Cary Denson (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Sunday, July 24, 2022

[FIXED] How to specify path to gorm structure file

 July 24, 2022     go-gin, go-gorm, json     No comments   

Issue

I have a Products structure:

type Products struct {
    gorm.Model
    CategoriesRefer   int64      `json:"cat_id" gorm:"column:cat_id"`
    Title             string     `json:"title" gorm:"column:title"`
    ...
    Image             string     `json:"image" gorm:"column:image"`
}

The JSON request will contain the path to the image, which is stored in another folder of my project(not in db folder), how can I specify this in the structure?


Solution

I believe something like this should work.

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    ex, err := os.Executable()
    if err != nil {
        panic(err)
    }
    exPath := filepath.Dir(ex)
    imagePath := fmt.Sprintf("%s/../photos", exPath) // change photos to correct directory name
    fmt.Println("imagePath:", imagePath)
}

However as mentioned in comments this isn't a great idea as it will be relative to the directory where the compiled binary executable is located. You may want to use something like an Environment Variable to control this directory. That way way you can set it to different directories for testing, development, production, etc...



Answered By - Pitchinnate
Answer Checked By - Timothy Miller (PHPFixing Admin)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Older Posts Home

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
All Comments
Atom
All Comments

Copyright © PHPFixing