Files
whatshooked/tooldoc/CODE_GUIDELINES.md
Hein f9773bd07f
Some checks failed
CI / Test (1.23) (push) Failing after -22m46s
CI / Test (1.22) (push) Failing after -22m32s
CI / Build (push) Failing after -23m30s
CI / Lint (push) Failing after -23m12s
refactor(API): Relspect integration
2026-02-05 13:39:43 +02:00

6.6 KiB

Code Guidelines for WhatsHooked

General Principles

  • Write clean, idiomatic Go code following standard conventions
  • Use meaningful variable and function names
  • Keep functions small and focused on a single responsibility
  • Document exported functions, types, and packages
  • Handle errors explicitly, never ignore them
  • Use context.Context for cancellation and timeout handling

Project Structure

whatshooked/
├── cmd/                    # Command-line entry points
│   ├── server/            # HTTP server command
│   └── cli/               # CLI tools
├── pkg/                    # Public packages
│   ├── auth/              # Authentication & authorization
│   ├── api/               # API endpoints
│   ├── cache/             # Caching layer
│   ├── config/            # Configuration management
│   ├── events/            # Event system
│   ├── eventlogger/       # Event logging
│   ├── handlers/          # HTTP handlers
│   ├── hooks/             # Webhook management
│   ├── logging/           # Structured logging
│   ├── storage/           # Database storage layer
│   ├── utils/             # Utility functions
│   ├── webserver/         # Web server with ResolveSpec
│   ├── whatsapp/          # WhatsApp integration
│   └── whatshooked/       # Core application logic
└── tooldoc/               # Tool & library documentation

Naming Conventions

Packages

  • Use lowercase, single-word package names
  • Use descriptive names that reflect the package's purpose
  • Avoid generic names like util or common (use utils with specific subdirectories if needed)

Files

  • Use snake_case for file names: user_service.go, auth_middleware.go
  • Test files: user_service_test.go
  • Keep related functionality in the same file

Variables & Functions

  • Use camelCase for private: userService, handleRequest
  • Use PascalCase for exported: UserService, HandleRequest
  • Use descriptive names: prefer userRepository over ur
  • Boolean variables should be prefixed: isValid, hasPermission, canAccess

Constants

  • Use PascalCase for exported: DefaultTimeout
  • Use camelCase for private: defaultTimeout
  • Group related constants together

Interfaces

  • Name interfaces with -er suffix when appropriate: Reader, Writer, Handler
  • Use descriptive names: UserRepository, AuthService

Error Handling

// Wrap errors with context
if err != nil {
    return fmt.Errorf("failed to load user: %w", err)
}

// Check specific errors
if errors.Is(err, sql.ErrNoRows) {
    return nil, ErrUserNotFound
}

// Custom error types
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

Logging

Use zerolog for structured logging:

import "github.com/rs/zerolog/log"

log.Info().
    Str("user_id", userID).
    Msg("User logged in")

log.Error().
    Err(err).
    Str("operation", "database_query").
    Msg("Failed to query database")

Context Usage

Always pass context as the first parameter:

func ProcessRequest(ctx context.Context, userID string) error {
    // Check for cancellation
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
    }
    
    // Pass context to downstream calls
    user, err := userRepo.GetByID(ctx, userID)
    if err != nil {
        return err
    }
    
    return nil
}

Testing

  • Write table-driven tests
  • Use descriptive test names: TestUserService_Create_WithValidData_Success
  • Mock external dependencies
  • Aim for high coverage of business logic
  • Use t.Run for subtests
func TestUserService_Create(t *testing.T) {
    tests := []struct {
        name    string
        input   *User
        wantErr bool
    }{
        {
            name: "valid user",
            input: &User{Name: "John", Email: "john@example.com"},
            wantErr: false,
        },
        {
            name: "missing email",
            input: &User{Name: "John"},
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            service := NewUserService()
            err := service.Create(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

Database Operations

  • Use transactions for multiple related operations
  • Always use prepared statements
  • Handle NULL values properly
  • Use meaningful struct tags
type User struct {
    ID        string    `gorm:"primaryKey" json:"id"`
    Name      string    `gorm:"not null" json:"name"`
    Email     string    `gorm:"uniqueIndex;not null" json:"email"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

ResolveSpec Integration

When using ResolveSpec:

  • Register all models in the registry
  • Use schema.table format: "public.users", "core.accounts"
  • Implement hooks for auth and validation
  • Use lifecycle hooks for audit logging

API Design

  • Use RESTful conventions
  • Return appropriate HTTP status codes
  • Include meaningful error messages
  • Version your APIs: /api/v1/users
  • Document all endpoints

Security

  • Never log sensitive data (passwords, tokens, etc.)
  • Validate all input
  • Use parameterized queries
  • Implement rate limiting
  • Use HTTPS in production
  • Sanitize user input
  • Implement proper CORS policies

Dependencies

  • Keep dependencies minimal
  • Pin versions in go.mod
  • Regularly update dependencies
  • Document why each dependency is needed

Documentation

  • Document all exported functions, types, and packages
  • Use godoc format
  • Include examples in documentation
  • Keep README.md up to date
  • Document configuration options

Comments

// Package auth provides authentication and authorization functionality.
package auth

// User represents a system user.
type User struct {
    ID string
}

// Authenticate verifies user credentials and returns a token.
// Returns ErrInvalidCredentials if authentication fails.
func Authenticate(ctx context.Context, username, password string) (string, error) {
    // Implementation
}

Performance

  • Use connection pools for databases
  • Implement caching where appropriate
  • Avoid N+1 queries
  • Use batch operations when possible
  • Profile critical paths
  • Set appropriate timeouts

Git Workflow

  • Write clear commit messages
  • Keep commits atomic and focused
  • Use conventional commits format
  • Create feature branches
  • Run tests before committing
  • Review your own changes before pushing