Skip to content
/ centra Public

Centra - Golang Centralized HTTP Error Handling through Error Matching

License

Notifications You must be signed in to change notification settings

otaxhu/centra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Centralized HTTP Error Handling


Tired of writing the following code?:

func GetUser(w http.ResponseWriter, r *http.Request) {
    user, err := userService.GetUser()
    if errors.Is(err, userService.ErrNotFound) {
        handleUserNotFound(w, r, err)
        return
    } else if errors.Is(err, userService.ErrBadSearchParameters) {
        handleBadSearchParameters(w, r, err)
        return
    }

    // Return user and OK response to client
}

func PostUser(w http.ResponseWriter, r *http.Request) {
    var user models.User
    err := userService.PostUser(user)
    if errors.Is(err, userService.ErrAlreadyExist) {
        handleUserAlreadyExist(w, r, err)
        return
    } else if errors.Is(err, userService.ErrBadSearchParameters) {
        handleUserBadSearchParameters(w, r, err)
        return
    }

    // Return OK response to client
}

Clearly in the previous example there is a duplication of code, this duplication may not be a problem in small APIs, but when you start adding more services, this code will not scale fine.

With Centra as your HTTP Error handler, the previous example can be replaced with:

func GetUser(w http.ResponseWriter, r *http.Request) {
    user, err := userService.GetUser()
    if err != nil {
        centra.Error(w, r, err)
        return
    }

    // Return user and OK response to client
}

func PostUser(w http.ResponseWriter, r *http.Request) {
    var user models.User
    err := userService.PostUser(user)
    if err != nil {
        centra.Error(w, r, err)
        return
    }
}

You will need to register HTTP handlers for each of the errors of your services:

func main() {
    mux := chi.NewMux()

    errMux := centra.NewMux()

    errMux.Handle(userService.ErrNotFound, func(w http.ResponseWriter, r *http.Request, err error) {
        w.WriteHeader(http.StatusNotFound)
    })

    errMux.Handle(userService.ErrAlreadyExist, func(w http.ResponseWriter, r *http.Request, err error) {
        w.WriteHeader(http.StatusConflict)
    })

    errMux.Handle(userService.ErrBadSearchParameters, func(w http.ResponseWriter, r *http.Request, err error) {
        w.WriteHeader(http.StatusBadRequest)
    })

    // Optionally you can set unknown handler, by default is DefaultUnknownHandler,
    // which just sends "Internal Server Error" string with "text/html" Content-Type
    errMux.HandleUnknown(func(w http.ResponseWriter, r *http.Request, err error) {
        w.WriteHeader(http.StatusInternalServerError)
    })

    // Build method returns the error handler as a middleware function, compatible with
    // Chi router
    errMw := errMux.Build()
    mux.Use(errMw)

    mux.Get("/users", GetUser)
    mux.Post("/users", PostUser)

    http.ListenAndServe(":8080", mux)
}

If you are using dinamically generated errors, It's recommended you use fmt.Errorf() with %w format placeholder to wrap a statically generated error, for example:

// service/user.go
func GetUser() (*models.User, error) {
    // There is an error, you'd like to generate a dynamic error
    return nil, fmt.Errorf("userService.GetUser: bad search parameters: error %w", ErrBadSearchParameters)
}

// controllers/user.go
func GetUser(w http.ResponseWriter, r *http.Request) {
    user, err := userService.GetUser()
    if err != nil {
        // The library will know that err is a ErrBadSearchParameters
        centra.Error(w, r, err)
        return
    }
}

The library will know that the underlying error is ErrBadSearchParameters and will match succesfully with the registered error handler.

NOTE: library will not unwrap the error, it will pass it to the error handler just as it is.

About

Centra - Golang Centralized HTTP Error Handling through Error Matching

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages