Custom Error
Dokumentasi custom error handling di repository layer.
CustomError Struct
type CustomError struct {
Message string
Err error
Code int
}
Fields
| Field | Type | Deskripsi |
|---|---|---|
Message | string | Pesan error yang user-friendly |
Err | error | Original error (untuk logging) |
Code | int | HTTP 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:
| Parameter | Type | Deskripsi |
|---|---|---|
message | string | Pesan error |
err | error | Original error (bisa nil) |
code | int | HTTP 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
| Aspect | CustomError | ResponseError |
|---|---|---|
| Location | Repository layer | Controller/Helper layer |
| Original Error | Yes (Err field) | No |
| Use Case | Database operations | HTTP responses |
| Logging | Contains original error for logging | User-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
| Situation | HTTP Code |
|---|---|
| Record not found | 404 |
| Duplicate key | 409 (Conflict) |
| Invalid foreign key | 400 |
| Database connection error | 503 |
| Other database errors | 500 |
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"} │
└─────────────┘ └─────────────┘ └─────────────┘