Skip to main content

Custom Error

Dokumentasi custom error handling di repository layer.

CustomError Struct

type CustomError struct {
Message string
Err error
Code int
}

Fields

FieldTypeDeskripsi
MessagestringPesan error yang user-friendly
ErrerrorOriginal error (untuk logging)
CodeintHTTP status code

Methods

Error()

Mengimplementasikan interface error.

func (e *CustomError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}

Output:

// Dengan original error
"Failed to save user: duplicate key value violates unique constraint"

// Tanpa original error
"Failed to save user"

Factory Function

CustomErrorMsg

Membuat instance CustomError baru.

func CustomErrorMsg(message string, err error, code int) *CustomError

Parameters:

ParameterTypeDeskripsi
messagestringPesan error
errerrorOriginal error (bisa nil)
codeintHTTP status code

Example:

err := repository.CustomErrorMsg(
"User not found",
gorm.ErrRecordNotFound,
http.StatusNotFound,
)

Usage in Repository

func GetUserByEmail(email string) (*models.User, error) {
var user models.User

if err := database.DB.Where("email = ?", email).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, CustomErrorMsg(
"User with email not found",
err,
http.StatusNotFound,
)
}
return nil, CustomErrorMsg(
"Failed to query user",
err,
http.StatusInternalServerError,
)
}

return &user, nil
}

Usage in Controller

func GetUser(c *gin.Context) {
email := c.Param("email")

user, err := repository.GetUserByEmail(email)
if err != nil {
if customErr, ok := err.(*repository.CustomError); ok {
// Log original error
logger.Errorf("Repository error: %v", customErr.Err)

// Send user-friendly response
c.JSON(customErr.Code, helpers.ResponseError{
Code: customErr.Code,
Status: false,
Message: customErr.Message,
})
return
}

// Fallback for unknown errors
helpers.SendError(c, helpers.NewInternalError("An error occurred"))
return
}

c.JSON(http.StatusOK, user)
}

Comparison: CustomError vs helpers.ResponseError

AspectCustomErrorResponseError
LocationRepository layerController/Helper layer
Original ErrorYes (Err field)No
Use CaseDatabase operationsHTTP responses
LoggingContains original error for loggingUser-facing message only

Best Practices

1. Wrap Database Errors

if err := db.Create(&user).Error; err != nil {
return CustomErrorMsg("Failed to create user", err, 500)
}

2. Preserve Original Error for Logging

// In repository
return CustomErrorMsg("User not found", gorm.ErrRecordNotFound, 404)

// In controller
if customErr, ok := err.(*CustomError); ok {
logger.Errorf("Original error: %v", customErr.Err) // Log detail
sendResponse(customErr.Message) // Send generic message
}

3. Use Appropriate HTTP Codes

SituationHTTP Code
Record not found404
Duplicate key409 (Conflict)
Invalid foreign key400
Database connection error503
Other database errors500

4. Type Assertion

// Always check type assertion success
if customErr, ok := err.(*repository.CustomError); ok {
// Handle CustomError
} else {
// Handle other errors
}

Error Flow

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Database │────▶│ Repository │────▶│ Controller │
│ Error │ │ CustomError │ │ Response │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ gorm. │ │ "User not │ │ {"code":404,│
│ Err... │ │ found" │ │ "message": │
│ │ │ Code: 404 │ │ "User not │
│ │ │ Err: gorm. │ │ found"} │
└─────────────┘ └─────────────┘ └─────────────┘