11

Here https://github.com/astaxie/build-web-application-with-golang/blob/master/en/11.1.md described how to enhance error handling with custom router and custom error type according to http package.

type appError struct {
    Error   error
    Message string
    Code    int
}    

type appHandler func(http.ResponseWriter, *http.Request) *appError
// custom handler catching errors
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if e := fn(w, r); e != nil { // e is *appError, not os.Error.
        c := appengine.NewContext(r)
        c.Errorf("%v", e.Error)
        http.Error(w, e.Message, e.Code)
    }
}
// fetch data or return *appError
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        return &appError{err, "Record not found", 404}
    }
    if err := viewTemplate.Execute(w, record); err != nil {
        return &appError{err, "Can't display record", 500}
    }
    return nil
}

The aim is to make all the handlers returning *appError in case of error and write it into response in the router, so there is no need to call c.JSON(500, err) directly in the code of viewRecord.

How to do the same with Gin?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Dr.eel
  • 1,837
  • 3
  • 18
  • 28
  • Here https://github.com/gin-gonic/gin/issues/274 I found the similar discussion, but still not found the solution. – Dr.eel Jan 16 '18 at 08:34

1 Answers1

20

Here @manucorporat gin-developer say:

We encourage developers to use a middleware to handle the error responses, so they can split the error logic, from the normal flow logic.

In the Gin to implement centralized error handling your should .Use(Middleware) and in the path handler's code use gin.Context.Error() to attach an error info to the request context. The Middleware is aware of gin.Context.Errors and here you can read them and process as you wish after gin.Context.Next().

The code below.

Error handling Gin Middleware:

//
// APP error definition
//
type appError struct {
    Code     int    `json:"code"`
    Message  string `json:"message"`
}

//
// Middleware Error Handler in server package
//
func JSONAppErrorReporter() gin.HandlerFunc {
    return jsonAppErrorReporterT(gin.ErrorTypeAny)
}

func jsonAppErrorReporterT(errType gin.ErrorType) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        detectedErrors := c.Errors.ByType(errType)

        log.Println("Handle APP error")
        if len(detectedErrors) > 0 {
            err := detectedErrors[0].Err
            var parsedError *appError
            switch err.(type) {
            case *appError:
                parsedError = err.(*appError )
            default:
                parsedError = &appError{ 
                  code: http.StatusInternalServerError,
                  message: "Internal Server Error"
                }
            }
            // Put the error into response
            c.IndentedJSON(parsedError.Code, parsedError)
            c.Abort()
            // or c.AbortWithStatusJSON(parsedError.Code, parsedError)
            return
        }

    }
}

//
//  Report Error in app
//
func fetchSingleHostGroup(c *gin.Context) {
    hostgroupID := c.Param("id")

    hostGroupRes, err := getHostGroupResource(hostgroupID)

    if err != nil {
        // put the Error to gin.Context.Errors
        c.Error(err)
        return
    }
    // return data of OK
    c.JSON(http.StatusOK, *hostGroupRes)
}
//
// Server setup
//
func main() {
    router := gin.Default()
    router.Use(JSONAppErrorReporter())
    router.GET("/hostgroups/:id", fetchSingleHostGroup)
    router.Run(":3000")
}

Another error handling ideas can be found in:

Dr.eel
  • 1,837
  • 3
  • 18
  • 28
  • Hi. Can you explain to me what parsedError = err.(*appError ) means in switch case? Thanks! – Mateusz Gebroski Nov 24 '20 at 07:00
  • @MateuszGebroski `parsedError = err.(*appError)` is a type assertion. That way you get the type `appError` out of the `err` (see https://tour.golang.org/methods/15 or https://stackoverflow.com/questions/38816843/explain-type-assertions-in-go) – chresse Dec 30 '20 at 20:04