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

260 lines
6.6 KiB
Markdown

# 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
```go
// 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:
```go
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:
```go
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
```go
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
```go
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
```go
// 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