Claude-skill-registry brokle-error-handling
Use this skill when implementing, reviewing, or debugging error handling in the Brokle codebase. This includes repository error mapping, service layer AppError constructors, handler response mapping, domain alias imports, or understanding the industrial error handling flow.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/brokle-error-handling" ~/.claude/skills/majiayu000-claude-skill-registry-brokle-error-handling && rm -rf "$T"
manifest:
skills/data/brokle-error-handling/SKILL.mdsource content
Brokle Error Handling Skill
Expert guidance for implementing industrial error handling patterns across all layers.
Industrial Error Handling Pattern
Core Flow:
Repository (Domain Errors) → Service (AppErrors) → Handler (HTTP Response)
Three-Layer Clean Architecture:
- Repository Layer: Domain errors with context wrapping
- Service Layer: AppError constructors for business logic
- Handler Layer: Centralized
handlingresponse.Error() - Zero Logging: Core services focus on pure business logic
Layer Responsibilities
| Layer | Responsibility | Error Pattern | Example |
|---|---|---|---|
| Repository | Data access, domain error wrapping | | |
| Service | Business logic, AppError constructors | | |
| Handler | HTTP transport, response mapping | | Automatic HTTP status code mapping |
Domain Alias Pattern (MANDATORY)
// ✅ Correct - Professional domain alias pattern import ( "context" "fmt" "gorm.io/gorm" authDomain "brokle/internal/core/domain/auth" orgDomain "brokle/internal/core/domain/organization" userDomain "brokle/internal/core/domain/user" "brokle/pkg/ulid" ) // ❌ Incorrect - Direct domain imports import ( "brokle/internal/core/domain/auth" )
Standard Aliases:
| Domain | Alias | Usage |
|---|---|---|
| auth | | , |
| organization | | |
| user | | |
| billing | | |
| observability | | |
Repository Layer Pattern
package auth import ( "context" "fmt" "gorm.io/gorm" authDomain "brokle/internal/core/domain/auth" "brokle/pkg/ulid" ) type userRepository struct { db *gorm.DB } // ✅ Correct pattern func (r *userRepository) GetByID(ctx context.Context, id ulid.ULID) (*authDomain.User, error) { var user authDomain.User err := r.db.WithContext(ctx).Where("id = ?", id).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // ✅ Standardized pattern return nil, fmt.Errorf("get user by ID %s: %w", id, authDomain.ErrNotFound) } return nil, fmt.Errorf("database query failed for user ID %s: %w", id, err) } return &user, nil } // ✅ Correct pattern func (r *userRepository) Create(ctx context.Context, user *authDomain.User) error { if err := r.db.WithContext(ctx).Create(user).Error; err != nil { return fmt.Errorf("create user %s: %w", user.Email, err) } return nil }
Repository Requirements (
internal/infrastructure/repository/user/user_repository.go:37-54):
✅ Required Pattern:
// 1. Professional domain alias imports import ( "errors" "fmt" "gorm.io/gorm" userDomain "brokle/internal/core/domain/user" ) // 2. GORM error checking with errors.Is() (STANDARD PATTERN) func (r *userRepository) GetByID(ctx context.Context, id ulid.ULID) (*userDomain.User, error) { var u userDomain.User err := r.db.WithContext(ctx).Where("id = ? AND deleted_at IS NULL", id).First(&u).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // ✅ Standardized across all repos return nil, fmt.Errorf("get user by ID %s: %w", id, userDomain.ErrNotFound) } return nil, fmt.Errorf("database query failed for user ID %s: %w", id, err) } return &u, nil }
Standard: All repositories use
errors.Is() for GORM error checking (standardized across codebase for wrapped error compatibility).
Service Layer Pattern
package auth import ( "context" "errors" authDomain "brokle/internal/core/domain/auth" "brokle/pkg/apperrors" "brokle/pkg/ulid" ) type userService struct { userRepo authDomain.UserRepository } // ✅ Correct pattern func (s *userService) GetUser(ctx context.Context, id ulid.ULID) (*GetUserResponse, error) { user, err := s.userRepo.GetByID(ctx, id) if err != nil { // Check for domain errors and convert to AppErrors if errors.Is(err, authDomain.ErrNotFound) { return nil, appErrors.NewNotFoundError("User") // resource string } return nil, appErrors.NewInternalError("Failed to retrieve user", err) } return &GetUserResponse{User: user}, nil } // ✅ Correct pattern with validation func (s *userService) CreateUser(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error) { // Input validation - NewValidationError(message, details string) if err := req.Validate(); err != nil { return nil, appErrors.NewValidationError("Invalid user data", err.Error()) } // Check for existing user existingUser, err := s.userRepo.GetByEmail(ctx, req.Email) if err != nil && !errors.Is(err, authDomain.ErrNotFound) { return nil, appErrors.NewInternalError("Failed to check existing user", err) } if existingUser != nil { return nil, appErrors.NewConflictError("User already exists with this email") } // Create user user := &authDomain.User{ ID: ulid.New(), Email: req.Email, Name: req.Name, } if err := s.userRepo.Create(ctx, user); err != nil { return nil, appErrors.NewInternalError("Failed to create user", err) } return &CreateUserResponse{User: user}, nil }
Service Requirements:
✅ Required (with exact signatures):
// 1. AppError constructors for business logic if errors.Is(err, userDomain.ErrNotFound) { return nil, appErrors.NewNotFoundError("User") // resource string } // 2. Validation errors (message, details string) return nil, appErrors.NewValidationError("Invalid email format", "email must be valid") // 3. Internal errors (message string, err error) return nil, appErrors.NewInternalError("Failed to process request", err)
❌ Prohibited:
// Don't use fmt.Errorf or errors.New in services return nil, fmt.Errorf("user not found") // ❌ // Don't include logging in core services log.Error("Failed to create user") // ❌
AppError Constructors (see
pkg/errors/errors.go:86-136 for complete list):
Common constructors:
appErrors.NewValidationError(message, details string) *AppError appErrors.NewNotFoundError(resource string) *AppError appErrors.NewConflictError(message string) *AppError appErrors.NewUnauthorizedError(message string) *AppError appErrors.NewForbiddenError(message string) *AppError appErrors.NewInternalError(message string, err error) *AppError appErrors.NewRateLimitError(message string) *AppError // Helper appErrors.IsAppError(err error) (*AppError, bool)
Reference: See
pkg/errors/errors.go for all constructors (BadRequest, ServiceUnavailable, NotImplemented, PaymentRequired, QuotaExceeded, AIProvider) and exact signatures
Handler Layer Pattern
package http import ( "github.com/gin-gonic/gin" authDomain "brokle/internal/core/domain/auth" "brokle/pkg/response" "brokle/pkg/ulid" "brokle/pkg/apperrors" ) type userHandler struct { userService authDomain.UserService } // ✅ Correct pattern func (h *userHandler) GetUser(c *gin.Context) { // 1. Validate input userID := c.Param("id") id, err := ulid.Parse(userID) if err != nil { response.Error(c, appErrors.NewValidationError("Invalid user ID format", "id must be a valid ULID")) return } // 2. Call service resp, err := h.userService.GetUser(c.Request.Context(), id) if err != nil { response.Error(c, err) // Automatic HTTP status mapping return } // 3. Return success response.Success(c, resp) }
Handler Requirements:
✅ Required:
// 1. Structured response handling resp, err := h.service.Method(c, req) if err != nil { response.Error(c, err) // Automatic status mapping return } response.Success(c, resp) // 2. Input validation before service calls if err := req.Validate(); err != nil { response.Error(c, appErrors.NewValidationError("Invalid request", err)) return }
❌ Prohibited:
// Don't inspect or log errors manually if errors.Is(err, apperrors.ErrNotFound) { // ❌ c.JSON(404, gin.H{"error": "Not found"}) } // Always use response.Error() response.Error(c, err) // ✅
HTTP Status Code Mapping
| AppError Type | HTTP Status | Description |
|---|---|---|
| ValidationError | 400 Bad Request | Invalid input data |
| UnauthorizedError | 401 Unauthorized | Authentication required |
| ForbiddenError | 403 Forbidden | Insufficient permissions |
| NotFoundError | 404 Not Found | Resource not found |
| ConflictError | 409 Conflict | Resource already exists |
| RateLimitError | 429 Too Many Requests | Rate limit exceeded |
| InternalError | 500 Internal Server Error | Unexpected server error |
Standard Error Response Format
{ "error": { "type": "VALIDATION_ERROR", "message": "Validation failed", "details": { "email": "must be a valid email address", "password": "must be at least 8 characters" }, "code": "INVALID_INPUT", "request_id": "req_01H4XJZQX3EXAMPLE" } }
Best Practices Checklist
Repository Layer:
- Use professional domain alias imports
- Convert GORM errors to domain errors with context
- No
or business logicerrors.New() - All database operations include error context
Service Layer:
- Use AppError constructors only
- Convert domain errors to business errors
- No logging in core services
- Validate inputs with meaningful error messages
Handler Layer:
- Use
for all error responsesresponse.Error() - Validate input before calling services
- No business logic in handlers
- Return appropriate HTTP status codes
References
- Complete industrial patternsdocs/development/ERROR_HANDLING_GUIDE.md
- Professional import patternsdocs/development/DOMAIN_ALIAS_PATTERNS.md
- Developer cheat sheetdocs/development/ERROR_HANDLING_QUICK_REFERENCE.md