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?
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:
- gin-gonic issue "Question: Handling errors"
- gin-gonic issue "Question: Status codes in error handling"
- chirp
- gin-merry error handler
- gin-frsh-showerrors