Claude Code Plugins

Community-maintained marketplace

Feedback

brokle-error-handling

@brokle-ai/brokle
2
0

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name brokle-error-handling
description 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.

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 response.Error() handling
  • Zero Logging: Core services focus on pure business logic

Layer Responsibilities

Layer Responsibility Error Pattern Example
Repository Data access, domain error wrapping fmt.Errorf("context: %w", domainError) fmt.Errorf("get user by ID %s: %w", id, userDomain.ErrNotFound)
Service Business logic, AppError constructors appErrors.NewNotFoundError() return nil, appErrors.NewNotFoundError("User not found")
Handler HTTP transport, response mapping response.Error(c, err) 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 authDomain authDomain.User, authDomain.ErrNotFound
organization orgDomain orgDomain.Organization
user userDomain userDomain.User
billing billingDomain billingDomain.Subscription
observability obsDomain obsDomain.Trace

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 errors.New() or business logic
  • 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 response.Error() for all error responses
  • Validate input before calling services
  • No business logic in handlers
  • Return appropriate HTTP status codes

References

  • docs/development/ERROR_HANDLING_GUIDE.md - Complete industrial patterns
  • docs/development/DOMAIN_ALIAS_PATTERNS.md - Professional import patterns
  • docs/development/ERROR_HANDLING_QUICK_REFERENCE.md - Developer cheat sheet