# Table of Contents

1. [REST API Standards](#rest-api-standards)
2. [Naming Conventions](#naming-conventions)
3. [HTTP Methods](#http-methods)
4. [Status Codes](#status-codes)
5. [Response Envelope](#response-envelope)
6. [SOLID Guidelines](#solid-guidelines)
7. [Critical Rules](#critical-rules)
8. [Error Handling](#error-handling)
9. [Testing Patterns](#testing-patterns)

---

## REST API Standards

### URL Naming

| Pattern | Correct | Incorrect |
|---------|---------|-----------|
| Resources | `/users` | `/getUsers` |
| Nested | `/users/123/orders` | `/getUserOrders` |
| Actions | `/users/123/activate` | `/activateUser` |
| Plural | Always plural | Singular |

### Query Parameters

```
GET /users?page=1&per_page=20
GET /users?search=name:john&sort=-created_at
GET /users?filter[is_active]=true&fields=nip,name
```

---

## Naming Conventions

| Element | Convention | Example |
|---------|-----------|---------|
| Packages | lowercase, single word | `domain`, `usecase`, `persistence` |
| Structs | PascalCase | `UserService`, `ProjectHandler` |
| Interfaces | PascalCase | `UserRepository`, `CacheRepository` |
| Functions | PascalCase (exported), camelCase (unexported) | `NewUserService`, `getByNIP` |
| Variables | camelCase | `projectID`, `isActive` |
| Constants | PascalCase | `MaxRetries`, `DefaultTimeout` |
| Database columns | snake_case | `project_id`, `created_at` |
| JSON fields | snake_case | `project_id`, `channel_type` |
| Environment vars | UPPER_SNAKE | `PERSISTENCE_DSN`, `LOG_LEVEL` |

---

## HTTP Methods

| Method | Usage | Response |
|--------|-------|----------|
| GET | Retrieve resource(s) | 200 + body |
| POST | Create new resource | 201 + body |
| PUT | Full replace | 200/204 + body |
| PATCH | Partial update | 200/204 + body |
| DELETE | Remove resource | 204 No Content |

---

## Status Codes

| Code | Usage |
|------|-------|
| 200 | GET success, PUT/PATCH success |
| 201 | POST create success |
| 204 | DELETE success, no body |
| 400 | Malformed request, validation error |
| 401 | Authentication required |
| 403 | Authenticated but forbidden |
| 404 | Resource not found |
| 409 | Conflict (duplicate, state violation) |
| 422 | Semantic validation error |
| 500 | Internal server error |
| 503 | Service unavailable (degraded health) |

---

## Response Envelope

```go
type Response struct {
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
    Meta    *Meta       `json:"meta,omitempty"`
}

type Meta struct {
    Page     int `json:"page,omitempty"`
    PerPage  int `json:"per_page,omitempty"`
    Total    int `json:"total,omitempty"`
    NextPage int `json:"next_page,omitempty"`
}
```

**Create User (POST /users):**

Request:
```json
{
  "name": "John Doe",
  "email": "john@example.com"
}
```

Response (201):
```json
{
  "data": {
    "nip": "p021050",
    "name": "John Doe",
    "email": "john@example.com",
    "is_active": true,
    "created_at": "2024-01-15T10:00:00Z"
  }
}
```

**List Users (GET /users):**

Response (200):
```json
{
  "data": [
    {"nip": "p021050", "name": "John Doe", ...},
    {"nip": "p021051", "name": "Jane Doe", ...}
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 2
  }
}
```

**Validation Error (400):**

```json
{
  "error": "validation failed",
  "meta": {
    "fields": {
      "email": "invalid email format"
    }
  }
}
```

---

## SOLID Guidelines

### Keep Interfaces Small (ISP)

**Good:**
```go
type UserReader interface {
    GetByNIP(ctx context.Context, nip string) (*User, error)
}
```

**Bad:**
```go
type UserManager interface {
    CreateUser()
    UpdateUser()
    DeleteUser()
    ListUsers()
    SearchUsers()
    ValidateUser()
    ActivateUser()
    ExportUsers()
    ImportUsers()
}
```

### Constructor Injection

**Good:**
```go
func NewProvider(cfg *Config) (*Provider, error) {
    pool, err := persistence.NewPool(...)
    if err != nil {
        return nil, err
    }
    return &Provider{pool: pool}, nil
}
```

**Bad:**
```go
func NewProvider() *Provider {
    return &Provider{
        pool: createPool(),  // Hidden dependency
    }
}
```

---

## Critical Rules

### NEVER Skip Error Checking

**Bad:**
```go
userRepo.Create(ctx, user)  // Ignored error!

rows, _ := db.Query(ctx, "SELECT * FROM users")  // Silent failure
```

**Good:**
```go
if err := userRepo.Create(ctx, user); err != nil {
    return fmt.Errorf("create user: %w", err)
}

rows, err := db.Query(ctx, "SELECT * FROM users")
if err != nil {
    return nil, fmt.Errorf("query users: %w", err)
}
```

**Named return for deferred close:**
```go
func processFile() (err error) {
    f, err := os.Open("file.txt")
    if err != nil {
        return err
    }
    defer func() {
        if cerr := f.Close(); cerr != nil && err == nil {
            err = cerr
        }
    }()
    return nil
}
```

---

## Error Handling

### Always Return Errors

**Bad:**
```go
func GetConfig() *Config {
    cfg, err := config.Load(ctx)
    if err != nil {
        panic(err)  // NEVER
    }
    return cfg
}
```

**Good:**
```go
func GetConfig() (*Config, error) {
    cfg, err := config.Load(ctx)
    if err != nil {
        return nil, fmt.Errorf("load config: %w", err)
    }
    return cfg, nil
}

func main() {
    cfg, err := GetConfig()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}
```

### Error Wrapping

```go
if err != nil {
    return fmt.Errorf("get user: %w", err)
}
```

---

## Testing Patterns

### Table-Driven Tests

```go
func TestUserService_GetByNIP(t *testing.T) {
    tests := []struct {
        name        string
        nip         string
        setupMock   func(*mockUserRepository)
        expectError bool
        expectNil   bool
    }{
        {
            name: "user found",
            nip:  "p021050",
            setupMock: func(m *mockUserRepository) {
                m.getByNIPFunc = func(nip string) (*User, error) {
                    return &User{NIP: nip, Name: "Test"}, nil
                }
            },
            expectError: false,
            expectNil:   false,
        },
        {
            name: "user not found",
            nip:  "nonexistent",
            setupMock: func(m *mockUserRepository) {
                m.getByNIPFunc = func(nip string) (*User, error) {
                    return nil, nil
                }
            },
            expectError: false,
            expectNil:   true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            mockRepo := &mockUserRepository{}
            tt.setupMock(mockRepo)

            svc := NewUserService(mockRepo)
            user, err := svc.GetByNIP(context.Background(), tt.nip)

            if tt.expectError && err == nil {
                t.Error("expected error, got nil")
            }
            if !tt.expectError && err != nil {
                t.Errorf("unexpected error: %v", err)
            }
            if tt.expectNil && user != nil {
                t.Error("expected nil user, got value")
            }
        })
    }
}
```

### Mock Repository

```go
type mockUserRepository struct {
    getByNIPFunc func(nip string) (*User, error)
    createFunc   func(user *User) error
}

func (m *mockUserRepository) GetByNIP(ctx context.Context, nip string) (*User, error) {
    if m.getByNIPFunc != nil {
        return m.getByNIPFunc(nip)
    }
    return nil, nil
}

func (m *mockUserRepository) Create(ctx context.Context, user *User) error {
    if m.createFunc != nil {
        return m.createFunc(user)
    }
    return nil
}
```

---

## Import Grouping

```go
import (
    // Standard library
    "context"
    "encoding/json"
    "fmt"
    "time"

    // Third-party packages
    "github.com/gin-gonic/gin"
    "github.com/jackc/pgx/v5"
    "github.com/pkg/errors"

    // Internal packages
    "messaging-be/internal/core/domain"
    "messaging-be/internal/core/port/inbound"
    "messaging-be/internal/core/port/outbound"
)
```
