AgentSkillsCN

gin-go

使用Gin框架审查Go代码,重点关注路由模式、中间件使用、请求/响应处理,以及错误处理规范。

SKILL.md
--- frontmatter
name: gin-go
description: Review Go code using Gin framework for routing patterns, middleware usage, request/response handling, and error conventions.
disable-model-invocation: false
allowed-tools: Read, Grep, Glob

Gin Framework Review

Review Go code using the Gin web framework for best practices in routing, middleware, request/response handling, and error management.

1. Router & Route Groups

Review router configuration for explicit control and consistent patterns.

Review Checklist

  • Issue if using gin.Default() without explicit middleware control
  • Issue if related routes aren't grouped with router.Group()
  • Issue if paths use camelCase or snake_case (should use kebab-case)
  • Issue if resource paths are singular (should be plural)
  • Issue if incorrect HTTP verbs for CRUD operations
go
// Issue: implicit middleware, no explicit control
router := gin.Default()

// Correct: explicit middleware configuration
router := gin.New()
router.Use(gin.Recovery())
router.Use(loggingMiddleware)
go
// Issue: ungrouped related routes
router.GET("/users/:id", h.GetUser)
router.POST("/users", h.CreateUser)
router.GET("/users/:id/orders", h.GetUserOrders)

// Correct: grouped routes
users := router.Group("/users")
{
    users.GET("/:id", h.GetUser)
    users.POST("", h.CreateUser)
    users.GET("/:id/orders", h.GetUserOrders)
}
go
// Issue: wrong path naming
router.GET("/getUserById/:id", h.GetUser)     // camelCase
router.GET("/user_orders", h.GetOrders)       // snake_case
router.GET("/user/:id", h.GetUser)            // singular

// Correct: kebab-case, plural resources
router.GET("/users/:id", h.GetUser)
router.GET("/user-orders", h.GetOrders)

2. Handler Structure

Handlers should be thin and delegate business logic to services.

Review Checklist

  • Issue if handlers contain business logic beyond HTTP concerns
  • Issue if handler structs use value receivers
  • Issue if errors aren't handled with early returns
  • Issue if parameter extraction is inconsistent
go
// Issue: business logic in handler
func (h UserHandler) Create(c *gin.Context) {  // value receiver
    var req CreateRequest
    c.BindJSON(&req)

    // Business logic - should be in service
    if req.Email != "" {
        // validate email format...
    }
    user := &User{Name: req.Name}
    h.db.Create(user)

    c.JSON(200, user)
}

// Correct: thin handler, pointer receiver
func (h *UserHandler) Create(c *gin.Context) {
    var req CreateRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.userService.Create(c.Request.Context(), &req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
        return
    }

    c.JSON(http.StatusCreated, user)
}

3. Middleware Patterns

Middleware should follow standard patterns for cross-cutting concerns.

Review Checklist

  • Issue if c.Next() is missing or misplaced in middleware
  • Issue if no recovery middleware for panic handling
  • Issue if logging middleware lacks request context (method, path, latency)
  • Issue if auth middleware doesn't abort on failure
go
// Issue: missing c.Next(), won't execute subsequent handlers
func LoggingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        log.Println("request received")
        // Missing c.Next()!
    }
}

// Correct: structured logging middleware
func LoggingMiddleware(logger *slog.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path

        c.Next()

        logger.Info("request",
            "method", c.Request.Method,
            "path", path,
            "status", c.Writer.Status(),
            "latency", time.Since(start),
        )
    }
}
go
// Issue: auth middleware doesn't abort
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(401, gin.H{"error": "unauthorized"})
            // Missing return! Continues to handler
        }
        c.Next()
    }
}

// Correct: abort on auth failure
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
            return
        }
        // Validate token...
        c.Set("userID", userID)
        c.Next()
    }
}

4. Request Binding & Validation

Use proper binding methods and struct tag validation.

Review Checklist

  • Issue if using BindJSON instead of ShouldBindJSON
  • Issue if binding errors aren't handled explicitly
  • Issue if required fields lack binding:"required" tag
  • Issue if validation errors expose internal details
go
// Issue: BindJSON aborts on error, loses control
func (h *Handler) Create(c *gin.Context) {
    var req Request
    c.BindJSON(&req)  // Aborts with 400 on error
    // Code below may not execute
}

// Correct: ShouldBindJSON for explicit error handling
func (h *Handler) Create(c *gin.Context) {
    var req Request
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "invalid request body",
            "details": formatValidationErrors(err),
        })
        return
    }
}
go
// Issue: missing validation tags
type CreateUserRequest struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}

// Correct: validation with struct tags
type CreateUserRequest struct {
    Email string `json:"email" binding:"required,email"`
    Name  string `json:"name" binding:"required,min=1,max=100"`
    Age   int    `json:"age" binding:"omitempty,gte=0,lte=150"`
}

5. Response Conventions

Maintain consistent response structure and status codes.

Review Checklist

  • Issue if JSON responses lack consistent structure
  • Issue if wrong HTTP status codes for operations
  • Issue if c.JSON called after c.Abort* methods
  • Issue if internal errors exposed to clients
go
// Issue: inconsistent response structures
c.JSON(200, user)                           // Direct object
c.JSON(200, gin.H{"user": user})            // Wrapped
c.JSON(200, gin.H{"data": user, "ok": true})// Different wrapper

// Correct: consistent response structure
type Response struct {
    Data  interface{} `json:"data,omitempty"`
    Error *ErrorInfo  `json:"error,omitempty"`
}

c.JSON(http.StatusOK, Response{Data: user})
go
// Issue: c.JSON after abort
func (h *Handler) Get(c *gin.Context) {
    if !authorized {
        c.AbortWithStatus(http.StatusUnauthorized)
        c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) // Wrong!
        return
    }
}

// Correct: use AbortWithStatusJSON
func (h *Handler) Get(c *gin.Context) {
    if !authorized {
        c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
        return
    }
}

HTTP Status Code Reference

OperationSuccessClient ErrorNot Found
GET200 OK400 Bad Request404 Not Found
POST (create)201 Created400/422-
PUT/PATCH200 OK400/422404 Not Found
DELETE204 No Content400404 Not Found

6. Context Usage

Handle Gin context correctly to avoid memory leaks and race conditions.

Review Checklist

  • Issue if *gin.Context is stored in structs or passed to goroutines
  • Issue if c is passed to service layer instead of c.Request.Context()
  • Issue if context values not retrieved safely with type assertion
  • Issue if context cancellation not handled in long operations
go
// Issue: storing gin.Context
type Handler struct {
    ctx *gin.Context  // Never do this!
}

// Issue: passing gin.Context to service
func (h *Handler) Get(c *gin.Context) {
    h.service.Process(c)  // Wrong! Pass standard context
}

// Correct: pass request context
func (h *Handler) Get(c *gin.Context) {
    result, err := h.service.Process(c.Request.Context())
}
go
// Issue: unsafe context value retrieval
func (h *Handler) Get(c *gin.Context) {
    userID := c.Get("userID").(string)  // Panics if not set or wrong type
}

// Correct: safe retrieval with MustGet or type check
func (h *Handler) Get(c *gin.Context) {
    userID, exists := c.Get("userID")
    if !exists {
        c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
        return
    }
    uid, ok := userID.(string)
    if !ok {
        c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "internal error"})
        return
    }
}
go
// Issue: gin.Context in goroutine
func (h *Handler) Process(c *gin.Context) {
    go func() {
        // c may be reused by this point!
        data := c.Query("data")
    }()
}

// Correct: copy needed values before goroutine
func (h *Handler) Process(c *gin.Context) {
    data := c.Query("data")
    ctx := c.Request.Context()
    go func() {
        // Use copied values
        processAsync(ctx, data)
    }()
}

7. Error Handling

Centralize error handling and avoid leaking internal details.

Review Checklist

  • Issue if no centralized error handling middleware
  • Issue if internal error messages exposed to clients
  • Issue if errors not logged with request context
  • Issue if custom error types don't map to HTTP status codes
go
// Issue: exposing internal errors
func (h *Handler) Get(c *gin.Context) {
    user, err := h.service.GetUser(c.Request.Context(), id)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})  // Leaks internal details!
        return
    }
}

// Correct: generic message, log internal error
func (h *Handler) Get(c *gin.Context) {
    user, err := h.service.GetUser(c.Request.Context(), id)
    if err != nil {
        h.logger.ErrorContext(c.Request.Context(), "failed to get user",
            "error", err,
            "user_id", id,
        )
        c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve user"})
        return
    }
}
go
// Correct: centralized error middleware with custom errors
type AppError struct {
    Code    string
    Message string
    Status  int
    Err     error
}

func ErrorMiddleware(logger *slog.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()

        if len(c.Errors) > 0 {
            err := c.Errors.Last().Err
            if appErr, ok := err.(*AppError); ok {
                logger.ErrorContext(c.Request.Context(), appErr.Message,
                    "error", appErr.Err,
                    "code", appErr.Code,
                )
                c.JSON(appErr.Status, gin.H{
                    "error": gin.H{
                        "code":    appErr.Code,
                        "message": appErr.Message,
                    },
                })
                return
            }
            // Unknown error
            logger.ErrorContext(c.Request.Context(), "unhandled error", "error", err)
            c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
        }
    }
}

Output Format

When reviewing Gin code, report findings as:

code
**Gin Framework Review**

Issues found:
- `handlers/user.go:15` - Using `gin.Default()`, prefer `gin.New()` with explicit middleware
- `handlers/user.go:42` - `BindJSON` should be `ShouldBindJSON` for explicit error handling
- `handlers/user.go:58` - `*gin.Context` passed to service, use `c.Request.Context()`
- `middleware/auth.go:23` - Missing `return` after `c.AbortWithStatusJSON`

Recommendations:
- Add centralized error handling middleware
- Group related routes under `/api/v1`
- Add `binding:"required"` tags to request struct fields

No issues:
- Recovery middleware configured
- Consistent JSON response structure
- Proper use of HTTP status codes

When to Run

This skill auto-invokes when reviewing Go files that:

  • Import github.com/gin-gonic/gin
  • Contain Gin handler functions (*gin.Context parameter)
  • Define middleware (gin.HandlerFunc return type)
  • Configure router setup (gin.New(), gin.Default(), router.Group())