# Go Gin Framework — SunLint Agent Guide

> Priority directives for AI agents working on Go + Gin projects.
> Rule files: `.agent/skills/sunlint-code-quality/rules/`

---

## Critical Patterns — Apply Every Time

### Middleware Chain Termination
- After sending a response that should stop the chain → `c.AbortWithStatusJSON(code, body)` then `return`
- `c.AbortWithStatusJSON` calls `c.Abort()` internally — don't call both
- Plain `return` from a handler does NOT stop downstream handlers
- See: `GN001-abort-after-response.md`

### Request Binding + Validation
- Every `c.ShouldBindJSON(&req)` → must be followed by `if err != nil { c.AbortWithStatusJSON(400, ...); return }`
- Declare all validation rules as struct `binding:` tags: `binding:"required,email"`, `binding:"min=1,max=255"`
- **Never** skip the error check from `ShouldBind*`
- See: `GN003-bind-error-handling.md`, `GN008-struct-validation-tags.md`

### Context Usage
- Inside handlers: `ctx := c.Request.Context()` — pass to all downstream calls
- **Never** `context.Background()` inside a handler
- **Never** pass `*gin.Context` to a goroutine — copy scalar values first, pass `c.Request.Context()`
- See: `GN002-request-context.md`, `GN010-context-scope.md`

### Do Not Log Sensitive DataTesting commands rõ ràng trong package.json/Makefile
Standard conventions (eslint, prettier, gofmt đã config sẵn)
4. Test với agents trước khi commit
Quy trình recommended:

Bước 1: Tạo baseline

# Chạy agent KHÔNG có AGENTS.md trên vài tasks nhỏ
# Đo success rate, cost
￼
Bước 2: Thêm AGENTS.md, measure lại

# So sánh với baseline
# Nếu không improve hoặc cost tăng quá nhiều → bỏ AGENTS.md
￼
Bước 3: Iterate

# Nếu quyết định giữ AGENTS.md, hãy giữ nó concise
# Monitor agent behavior qua time
￼
5. Monitor Context Length
Context files nên dưới 500 tokens (t
- Logging middleware logs only: method, path, status, duration, request_id, client IP
- **Never** log request bodies, `Authorization` header values, or any field named password/token/card
- See: `GN012-no-log-sensitive.md`

---

## Architecture Patterns

### Handler struct — dependency injection
Handlers are structs with injected service interfaces. No globals. No `new(Service)` inside handlers.
```go
type OrderHandler struct {
    service OrderService     // interface — mockable
    logger  *slog.Logger
}
func NewOrderHandler(svc OrderService, logger *slog.Logger) *OrderHandler {
    return &OrderHandler{service: svc, logger: logger}
}
```
See: `GN004-dependency-injection.md`

### Route organisation
```go
r := gin.New()
r.Use(gin.Recovery(), RequestID(), RequestLogger(logger))   // global

public := r.Group("/api/v1")
{ /* unauthenticated routes */ }

private := r.Group("/api/v1")
private.Use(JWTAuth(validator))
{ /* authenticated routes */ }
```
See: `GN005-route-groups-middleware.md`, `GN011-middleware-concerns.md`

### Production setup checklist
```go
gin.SetMode(os.Getenv("GIN_MODE"))    // "release" in prod — GN007
r := gin.New()
r.Use(CustomRecovery(logger))          // GN009
r.Use(RequestID())                     // GN011
r.Use(RequestLogger(logger))           // GN012
// ... register groups (GN005)
```

### HTTP Status Codes
- `POST` success → `c.JSON(http.StatusCreated, obj)` (201)
- `DELETE` no body → `c.Status(http.StatusNoContent)` (204)
- Not found → `c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "not found"})` (404)
- Validation error → `c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})` (400)
- **Always** use `net/http` constants — never magic integers
- See: `GN006-http-status-codes.md`

### Cross-cutting concerns
Auth, rate limiting, CORS, request ID, structured logging → **always middleware**, never inside handlers.
See: `GN011-middleware-concerns.md`

---

## Run Commands

```bash
# Development
go run ./cmd/api/main.go
GIN_MODE=debug go run ./cmd/api/main.go

# Build
go build -o bin/api ./cmd/api/

# Test (always with race detector)
go test -race ./...
go test -race -run TestOrderHandler ./internal/handlers/
go test -cover ./...

# Linting
golangci-lint run ./...
staticcheck ./...

# Env for production
GIN_MODE=release
PORT=8080
```

---

## What NOT to do (quick reference)

| ❌ Wrong | ✅ Correct |
|---|---|
| `return` after `c.JSON` in middleware | `c.AbortWithStatusJSON(...)` then `return` |
| `context.Background()` in handler | `c.Request.Context()` |
| `go func() { use c.Get(...) }()` | Copy value before goroutine: `val := c.GetString(...)` |
| `c.ShouldBindJSON(&req)` without err check | `if err := c.ShouldBindJSON(&req); err != nil { abort }` |
| Manual `if req.Email == ""` validation | `binding:"required,email"` struct tag |
| `var db *gorm.DB` package global | Inject `db *gorm.DB` as struct field |
| Auth check inside handler | `JWTAuth()` middleware on route group |
| `log.Printf("body: %s", body)` | Log method+path+status+duration only |
| `gin.Default()` in production | `gin.New()` + explicit `Recovery()` + structured logger |
| `c.JSON(200, gin.H{"error": ...})` | `c.AbortWithStatusJSON(http.StatusBadRequest, ...)` |
| `GIN_MODE` not set (debug default) | `GIN_MODE=release` in production environment |
