Skip to content

Commit

Permalink
refactor: refactoring error handler
Browse files Browse the repository at this point in the history
  • Loading branch information
felipeversiane committed Aug 22, 2024
1 parent d8143a7 commit 7e34fa7
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 72 deletions.
53 changes: 53 additions & 0 deletions internal/rest/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package rest

import "net/http"

type RestError struct {
Message string `json:"message"`
Err string `json:"error"`
Code int `json:"code"`
}

func (r *RestError) Error() string {
return r.Message
}

func NewBadRequestError(message string) *RestError {
return &RestError{
Message: message,
Err: "bad_request",
Code: http.StatusBadRequest,
}
}

func NewUnauthorizedRequestError(message string) *RestError {
return &RestError{
Message: message,
Err: "unauthorized",
Code: http.StatusUnauthorized,
}
}

func NewInternalServerError(message string) *RestError {
return &RestError{
Message: message,
Err: "internal_server_error",
Code: http.StatusInternalServerError,
}
}

func NewNotFoundError(message string) *RestError {
return &RestError{
Message: message,
Err: "not_found",
Code: http.StatusNotFound,
}
}

func NewForbiddenError(message string) *RestError {
return &RestError{
Message: message,
Err: "forbidden",
Code: http.StatusForbidden,
}
}
77 changes: 34 additions & 43 deletions internal/task/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"strings"

"github.com/felipeversiane/task-api/internal/rest"
"github.com/google/uuid"
)

Expand All @@ -23,22 +24,16 @@ func (h *TaskHandler) PostTask(w http.ResponseWriter, r *http.Request) {

var req TaskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
httpErr := rest.NewBadRequestError("Invalid request payload")
w.WriteHeader(httpErr.Code)
w.Write([]byte(httpErr.Error()))
return
}

if err := req.Validate(); err != nil {
respondWithError(w, http.StatusBadRequest, err.Error())
}

domain := RequestToDomainTask(req)
if err := domain.ValidateFields(); err != nil {
respondWithError(w, http.StatusBadRequest, err.Error())
}

resp, err := h.Service.CreateTask(ctx, domain)
resp, err := h.Service.CreateTask(ctx, req)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
w.WriteHeader(err.Code)
w.Write([]byte(err.Error()))
return
}

Expand All @@ -48,30 +43,26 @@ func (h *TaskHandler) PostTask(w http.ResponseWriter, r *http.Request) {
func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

id, err := extractIDFromPath(r.URL.Path)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid task ID")
id, parseErr := extractIDFromPath(r.URL.Path)
if parseErr != nil {
httpErr := rest.NewBadRequestError("Invalid task ID")
w.WriteHeader(httpErr.Code)
w.Write([]byte(httpErr.Error()))
return
}

var req UpdateTaskRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
httpErr := rest.NewBadRequestError("Invalid request payload")
w.WriteHeader(httpErr.Code)
w.Write([]byte(httpErr.Error()))
return
}

if err := req.Validate(); err != nil {
respondWithError(w, http.StatusBadRequest, err.Error())
}

domain := RequestToUpdateDomainTask(req)
if err := domain.ValidateFields(); err != nil {
respondWithError(w, http.StatusBadRequest, err.Error())
}

resp, err := h.Service.UpdateTask(ctx, id, domain)
resp, err := h.Service.UpdateTask(ctx, id, req)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
w.WriteHeader(err.Code)
w.Write([]byte(err.Error()))
return
}

Expand All @@ -81,14 +72,17 @@ func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

id, err := extractIDFromPath(r.URL.Path)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid task ID")
id, parseErr := extractIDFromPath(r.URL.Path)
if parseErr != nil {
httpErr := rest.NewBadRequestError("Invalid task ID")
w.WriteHeader(httpErr.Code)
w.Write([]byte(httpErr.Error()))
return
}

if err := h.Service.DeleteTask(ctx, id); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
w.WriteHeader(err.Code)
w.Write([]byte(err.Error()))
return
}

Expand All @@ -98,15 +92,18 @@ func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
func (h *TaskHandler) GetTaskByID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

id, err := extractIDFromPath(r.URL.Path)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid task ID")
id, parseErr := extractIDFromPath(r.URL.Path)
if parseErr != nil {
httpErr := rest.NewBadRequestError("Invalid task ID")
w.WriteHeader(httpErr.Code)
w.Write([]byte(httpErr.Error()))
return
}

resp, err := h.Service.GetTaskByID(ctx, id)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
w.WriteHeader(err.Code)
w.Write([]byte(err.Error()))
return
}

Expand All @@ -118,7 +115,8 @@ func (h *TaskHandler) GetAllTasks(w http.ResponseWriter, r *http.Request) {

resp, err := h.Service.GetAllTasks(ctx)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
w.WriteHeader(err.Code)
w.Write([]byte(err.Error()))
return
}

Expand All @@ -138,10 +136,3 @@ func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
w.WriteHeader(code)
w.Write(response)
}

func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]interface{}{
"code": code,
"message": message,
})
}
47 changes: 24 additions & 23 deletions internal/task/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

domain "github.com/felipeversiane/task-api/internal"
"github.com/felipeversiane/task-api/internal/rest"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
Expand All @@ -27,11 +28,11 @@ func NewTaskRepository(database *pgxpool.Pool, cache *redis.Client) TaskReposito
}
}

func (r *TaskRepository) Insert(ctx context.Context, task domain.Task) (*TaskResponse, error) {
func (r *TaskRepository) Insert(ctx context.Context, task domain.Task) (*TaskResponse, *rest.RestError) {
nameKey := fmt.Sprintf("task:name:%s", task.Name)

if v, err := r.Cache.Get(ctx, nameKey).Result(); err == nil && v != "" {
return nil, fmt.Errorf("task with name %s already exists", task.Name)
return nil, rest.NewBadRequestError(fmt.Sprintf("task with name %s already exists", task.Name))
}

query := `INSERT INTO tasks (id, name, description, situation, created_at, updated_at)
Expand All @@ -50,14 +51,14 @@ func (r *TaskRepository) Insert(ctx context.Context, task domain.Task) (*TaskRes
if cacheErr != nil {
slog.Error(fmt.Sprintf("Failed to cache task: %v", cacheErr))
}
return nil, fmt.Errorf("task with name %s already exists", task.Name)
return nil, rest.NewBadRequestError(fmt.Sprintf("task with name %s already exists", task.Name))
}
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}

taskJSON, err := json.Marshal(taskResponse)
if err != nil {
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}
_, err = r.Cache.Set(ctx, taskResponse.ID.String(), taskJSON, 24*time.Hour).Result()
if err != nil {
Expand All @@ -72,20 +73,20 @@ func (r *TaskRepository) Insert(ctx context.Context, task domain.Task) (*TaskRes
return &taskResponse, nil
}

func (r *TaskRepository) Update(ctx context.Context, id uuid.UUID, domain domain.Task) (*TaskResponse, error) {
nameKey := fmt.Sprintf("task:name:%s", domain.Name)
func (r *TaskRepository) Update(ctx context.Context, id uuid.UUID, task domain.Task) (*TaskResponse, *rest.RestError) {
nameKey := fmt.Sprintf("task:name:%s", task.Name)

existingID, err := r.Cache.Get(ctx, nameKey).Result()
if err == nil && existingID != "" && existingID != id.String() {
return nil, fmt.Errorf("task with name %s already exists", domain.Name)
return nil, rest.NewBadRequestError(fmt.Sprintf("task with name %s already exists", task.Name))
}

query := `UPDATE tasks SET name = $1, description = $2, situation = $3, updated_at = $4
WHERE id = $5
RETURNING id, name, description, situation, created_at, updated_at`

var taskResponse TaskResponse
err = r.Database.QueryRow(ctx, query, domain.Name, domain.Description, domain.Situation, domain.UpdatedAt, id).
err = r.Database.QueryRow(ctx, query, task.Name, task.Description, task.Situation, task.UpdatedAt, id).
Scan(&taskResponse.ID, &taskResponse.Name, &taskResponse.Description, &taskResponse.Situation,
&taskResponse.CreatedAt, &taskResponse.UpdatedAt)

Expand All @@ -95,14 +96,14 @@ func (r *TaskRepository) Update(ctx context.Context, id uuid.UUID, domain domain
if cacheErr != nil {
slog.Error(fmt.Sprintf("Failed to cache task existence: %v", cacheErr))
}
return nil, fmt.Errorf("task with name %s already exists", domain.Name)
return nil, rest.NewBadRequestError(fmt.Sprintf("task with name %s already exists", task.Name))
}
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}

taskJSON, err := json.Marshal(taskResponse)
if err != nil {
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}
_, err = r.Cache.Set(ctx, id.String(), taskJSON, 24*time.Hour).Result()
if err != nil {
Expand All @@ -117,7 +118,7 @@ func (r *TaskRepository) Update(ctx context.Context, id uuid.UUID, domain domain
return &taskResponse, nil
}

func (r *TaskRepository) Delete(ctx context.Context, id uuid.UUID) error {
func (r *TaskRepository) Delete(ctx context.Context, id uuid.UUID) *rest.RestError {
_, err := r.Cache.Del(ctx, id.String()).Result()
if err != nil {
slog.Error(fmt.Sprintf("Failed to delete task from cache: %v", err))
Expand All @@ -128,15 +129,15 @@ func (r *TaskRepository) Delete(ctx context.Context, id uuid.UUID) error {
err = r.Database.QueryRow(ctx, query, id).Scan(&deletedID)
if err != nil {
if err == pgx.ErrNoRows {
return fmt.Errorf("task with ID %s not found", id)
return rest.NewNotFoundError(fmt.Sprintf("task with ID %s not found", id))
}
return err
return rest.NewInternalServerError(fmt.Sprintf("%s", err))
}

return nil
}

func (r *TaskRepository) GetByID(ctx context.Context, id uuid.UUID) (*TaskResponse, error) {
func (r *TaskRepository) GetByID(ctx context.Context, id uuid.UUID) (*TaskResponse, *rest.RestError) {
taskJSON, err := r.Cache.Get(ctx, id.String()).Result()
if err == nil {
var task TaskResponse
Expand All @@ -150,14 +151,14 @@ func (r *TaskRepository) GetByID(ctx context.Context, id uuid.UUID) (*TaskRespon
var task TaskResponse
if err := row.Scan(&task.ID, &task.Name, &task.Description, &task.Situation, &task.CreatedAt, &task.UpdatedAt); err != nil {
if err == pgx.ErrNoRows {
return nil, fmt.Errorf("task with ID %s not found", id)
return nil, rest.NewNotFoundError(fmt.Sprintf("task with ID %s not found", id))
}
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}

taskBytes, err := json.Marshal(task)
if err != nil {
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}
_, err = r.Cache.Set(ctx, id.String(), string(taskBytes), 24*time.Hour).Result()
if err != nil {
Expand All @@ -167,24 +168,24 @@ func (r *TaskRepository) GetByID(ctx context.Context, id uuid.UUID) (*TaskRespon
return &task, nil
}

func (r *TaskRepository) GetAll(ctx context.Context) ([]TaskResponse, error) {
func (r *TaskRepository) GetAll(ctx context.Context) ([]TaskResponse, *rest.RestError) {
rows, err := r.Database.Query(ctx, `SELECT id, name, description, situation, created_at, updated_at FROM tasks`)
if err != nil {
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}
defer rows.Close()

var tasks []TaskResponse
for rows.Next() {
var task TaskResponse
if err := rows.Scan(&task.ID, &task.Name, &task.Description, &task.Situation, &task.CreatedAt, &task.UpdatedAt); err != nil {
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}
tasks = append(tasks, task)
}

if err := rows.Err(); err != nil {
return nil, err
return nil, rest.NewInternalServerError(fmt.Sprintf("%s", err))
}

return tasks, nil
Expand Down
Loading

0 comments on commit 7e34fa7

Please sign in to comment.