Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic error handling framework and 404 handling #50

Merged
merged 14 commits into from
Jul 18, 2023
13 changes: 9 additions & 4 deletions controller/users/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,28 @@ func HandleList(w http.ResponseWriter, r *http.Request) {
controller.EncodeJSONResponse(w, userviews.ListFrom(users))
}

func HandleRead(w http.ResponseWriter, r *http.Request) {
func HandleRead(w http.ResponseWriter, r *http.Request) error {
userIDStr := chi.URLParam(r, "userID")
userID, err := strconv.Atoi(userIDStr)
if err != nil {
http.Error(w, "Invalid userID", http.StatusBadRequest)
return
return err
}

// Get DB instance
db, err := database.GetDBFrom(r)
if err != nil {
logrus.Error(err)
panic(err)
return err
}

user := model.GetUserByID(db, userID)
user, err := model.GetUserByID(db, userID)
if err != nil {
logrus.Error(err)
return err
}
controller.EncodeJSONResponse(w, userviews.SingleFrom(user))
return nil
}

func HandleCreate(w http.ResponseWriter, r *http.Request) {
Expand Down
19 changes: 19 additions & 0 deletions internal/database/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package database

import (
"errors"
"fmt"

apierrors "github.com/source-academy/stories-backend/internal/errors"
"gorm.io/gorm"
)

func HandleDBError(err error, fromModel string) error {
if errors.Is(err, gorm.ErrRecordNotFound) {
return apierrors.ClientNotFoundError{
Message: fmt.Sprintf("Cannot find requested %s.", fromModel),
}
}
// TODO: Handle more types of errors
return err
}
17 changes: 17 additions & 0 deletions internal/errors/404.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package apierrors

import (
"net/http"
)

type ClientNotFoundError struct {
Message string
}

func (e ClientNotFoundError) Error() string {
return e.Message
}

func (e ClientNotFoundError) HTTPStatusCode() int {
return http.StatusNotFound
}
8 changes: 8 additions & 0 deletions internal/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package apierrors

// ClientError is an interface for errors that should be returned to the client.
// They generally start with a 4xx HTTP status code.
type ClientError interface {
error
HTTPStatusCode() int
}
28 changes: 28 additions & 0 deletions internal/router/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package router

import (
"errors"
"net/http"

apierrors "github.com/source-academy/stories-backend/internal/errors"
)

func handleAPIError(handler func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := handler(w, r)
if err == nil {
// No error, response already written
return
}

var clientError apierrors.ClientError
if errors.As(err, &clientError) {
// Client error (status 4xx), write error message and status code
http.Error(w, clientError.Error(), clientError.HTTPStatusCode())
return
}

// 500 Internal Server Error as a catch-all
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
2 changes: 1 addition & 1 deletion internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Setup(config *config.Config, injectMiddleWares []func(http.Handler) http.Ha

r.Route("/users", func(r chi.Router) {
r.Get("/", users.HandleList)
r.Get("/{userID}", users.HandleRead)
r.Get("/{userID}", handleAPIError(users.HandleRead))
r.Post("/", users.HandleCreate)
})

Expand Down
10 changes: 7 additions & 3 deletions model/users.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"github.com/source-academy/stories-backend/internal/database"
"gorm.io/gorm"
)

Expand All @@ -16,10 +17,13 @@ func GetAllUsers(db *gorm.DB) []User {
return users
}

func GetUserByID(db *gorm.DB, id int) User {
func GetUserByID(db *gorm.DB, id int) (User, error) {
var user User
db.First(&user, id)
return user
err := db.First(&user, id).Error
if err != nil {
return user, database.HandleDBError(err, "user")
}
return user, err
}

func CreateUser(db *gorm.DB, user *User) {
Expand Down