Go Deep understanding of custom error handling in REST API

313 views Asked by At

I am coming from the world of Node.js where I learn a lot from building web application using Express and there, there is a good way, I think, to handle when dealing with error and in case of unexpected error, there is a awesome way to catch it.

So, I looked for the same thing in Go. I don't know if I have already found it, but what I found come from this link https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/11.1.html and from some articles I read, it seems like many developers are using the same approach.

My concern, and not a real one, is that I don't understand some part of that code below.

type appHandler func(http.ResponseWriter, *http.Request) error

func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := fn(w, r); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

I know that it's possible to create custom type in go, but seriously I don't understand what this one means or how to understand it in the http.Serve

type appHandler func(http.ResponseWriter, *http.Request) error

and one of the thing that I don't catch is

func (fn AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

In most of the code I read, generally, it's a struct or a replaced type (I mean type Account int) but here, it's a function. I would like to understand how is it going to help in handling errors.

Inside the implementation of the ServeHTTP up there, we have this line err := fn(w, r). Please, can you explain it?

From the same article, we have this code

func viewRecord(w http.ResponseWriter, r *http.Request) error {
    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 err
    }
    return viewTemplate.Execute(w, record)
}

and this line

func init() {
    http.Handle("/view", appHandler(viewRecord))
}

Please, could you help me to understand this appHandler(viewRecord)? What is it exactly? It is an instantiation, is it casting? What is it supposed to do? I mean, how to understand that line that seems critical?

One last question, please. Is it possible to catch an error that might happen anywhere during the treatment of a request? In Node.js, you can just do something like

const app = express()

const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  if (err instanceof CustomError) {
    return res.status(err.statusCode).send({ errors: err.serializeErrors() });
  }
  
  res.status(500).send({ 
    errors: [{ message: err.message || 'Something went wrong' }]
  });
}

app.use(errorHandler())

Something like that, is it possible in Go?

Thank you.

1

There are 1 answers

2
nipuna On BEST ANSWER

In Go, we can define custom types from any primitive types, example:

type A int
type Person struct{}

This is common for functions al well. You can define a Go type with desired function signature like in appHandler type in above example. I have added simple example below.

package main

import (
    "errors"
    "fmt"
)

type IntFun func(a,b int) error
func main(){
    var addFunc IntFun
    addFunc = func(a, b int) error {
        if a ==0 || b == 0 {
            return errors.New(`zero valued inputs`)
        }
        fmt.Println(`sum := `, a+b)
        return nil
    }

    addFunc.Add(5, 3) //Output: sum :=  8
    addFunc.Add(0, 0) //Output: zero valued inputs
}

func (fn IntFun) Add(a, b int)  {
    err := fn(a,b)
    if err != nil {
        fmt.Println(err)
        return
    }
}

and also refer user-defined-function-type-go

In your case,

func init() {
    http.Handle("/view", appHandler(viewRecord))
}

This is the http endpoint handling part. "/view" is the path pattern and other argument is the handler for the request. That parameter should implement Handler interface in net/http Go package. So that's why app handler type have ServeHttp function.

viewRecord function is also compatible with type appHandler function type. it handle the request coming to "/view" endpoint. So it is casted to appHandler type and passed to http.Handle("/view", appHandler(viewRecord)) Handle function.

I think this will explain the scenario. Please comment if anything not clear.